import express from "express";
import dotenv from "dotenv";
import axios from "axios";
import { formatData } from "./formatter.js";
dotenv.config();
const app = express();

app.use(express.json());

app.get("/api/get-timetable/", async (req, res) => {
  const username = req.query.username; // required
  const password = req.query.password; // required
  const school = req.query.school; // required

  if (!username || !password || !school) {
    res.status(400).json({
      error: "Missing required query params: username, password, school",
    });
    return;
  }

  let startDate = req.query.startDate; // optional - expected format: YYYYMMDD
  let endDate = req.query.endDate; // optional - expected format: YYYYMMDD

  let sessionId = ""; // will be set after authentication by server response
  let personId = ""; // will be set after authentication by server response
  let personType = ""; // will be set after authentication by server response

  // static function for checking or/and generating start and end date for the current week
  function setDates() {
    if (startDate && endDate) {
      return;
    }

    const now = new Date();
    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    const dayOfWeek = today.getDay(); // 0 (Sunday) to 6 (Saturday)

    const addDays = (date, days) =>
      new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);

    // format dates to YYYYMMDD
    const formatDate = (date) => {
      const year = date.getFullYear();
      const month = String(date.getMonth() + 1).padStart(2, "0");
      const day = String(date.getDate()).padStart(2, "0");
      return `${year}${month}${day}`;
    };

    // Desired behavior:
    // - Weekday (Mon-Thu): startDate = today, endDate = coming Friday (same week)
    // - Friday: startDate = today, endDate = coming Friday (next week)
    // - Weekend (Sat/Sun): startDate = coming Monday, endDate = coming Friday (of that week)
    const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;

    let start;
    let end;

    if (isWeekend) {
      const daysUntilMonday = dayOfWeek === 0 ? 1 : 2; // Sun->Mon:1, Sat->Mon:2
      start = addDays(today, daysUntilMonday);
      end = addDays(start, 4); // Monday + 4 days = Friday
    } else {
      start = today;
      const daysUntilFriday = 5 - dayOfWeek; // Mon(1)->4 ... Fri(5)->0
      end = addDays(today, daysUntilFriday === 0 ? 7 : daysUntilFriday);
    }

    startDate = formatDate(start);
    endDate = formatDate(end);
  }

  setDates();

  const baseUrl = `https://${school}.webuntis.com/WebUntis/jsonrpc.do?school=${school}`;

  const toJsonRpcError = (data) => {
    const err = data?.error;
    if (!err) return null;
    const code = err.code;
    const message = err.message || "Unknown WebUntis error";
    return { code, message };
  };

  try {
    // 1) authenticate
    const authResponse = await axios.post(baseUrl, {
      id: "1",
      method: "authenticate",
      params: {
        user: username,
        password: password,
        client: "watch-untis",
      },
      jsonrpc: "2.0",
    });

    const authErr = toJsonRpcError(authResponse.data);
    if (authErr) {
      if (authErr.message === "bad credentials") {
        res.status(401).json({ error: "Invalid username or password" });
        return;
      }
      if (authErr.code === -8504) {
        res.status(403).json({ error: "WebUntis Error: -8504" });
        return;
      }
      res.status(502).json({ error: authErr.message, code: authErr.code });
      return;
    }

    sessionId = authResponse.data?.result?.sessionId;
    personId = authResponse.data?.result?.personId;
    personType = authResponse.data?.result?.personType;
    if (!sessionId || !personId) {
      res.status(502).json({
        error: "WebUntis auth succeeded but returned no sessionId/personId",
      });
      return;
    }

    // 2) get timetable (after we have sessionId, personId & personType from auth response)
    const timetableResponse = await axios.post(
      baseUrl,
      {
        id: "3",
        method: "getTimetable",
        params: {
          options: {
            element: {
              id: personId,
              type: personType,
            },
            startDate,
            endDate,
            teacherFields: ["id", "name", "longname"],
            subjectFields: ["id", "name", "longname"],
            roomFields: ["id", "name", "longname"],
          },
        },
        jsonrpc: "2.0",
      },
      {
        headers: {
          Cookie: `JSESSIONID=${sessionId}`,
        },
      },
    );

    const timetableErr = toJsonRpcError(timetableResponse.data);
    if (timetableErr) {
      res
        .status(502)
        .json({ error: timetableErr.message, code: timetableErr.code });
      return;
    }

    // 3) format the data once it successfully returned
    try {
      const formattedData = formatData(timetableResponse.data);
      res.json(formattedData);
    } catch (formatError) {
      console.error("formatData failed", formatError);
      res.status(500).json({ error: "Failed to format timetable data" });
    }

    // respond to client
    res.json(timetableResponse.data?.result ?? null);
    return;
  } catch (error) {
    const status = error?.response?.status;
    const data = error?.response?.data;
    const jsonRpcErr = toJsonRpcError(data);

    if (jsonRpcErr?.message === "bad credentials") {
      res.status(401).json({ error: "Invalid username or password" });
      return;
    }
    if (jsonRpcErr?.code === -8504) {
      res.status(403).json({ error: "WebUntis Error: -8504" });
      return;
    }

    console.error(error);
    res.status(502).json({
      error: "Failed to fetch timetable",
      upstreamStatus: status,
    });
  }
});

// error handling code
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send("Something broke!");
});

app.listen(8001, () => {
  console.log("Server is running on port 8001");
});
