import { Router } from "express";
import { getAuth } from "@clerk/express";
import { db } from "@workspace/db";
import { timetablesTable, sessionRowsTable } from "@workspace/db";
import { eq } from "drizzle-orm";

const router = Router();

interface TeachingSlot {
  dayOfWeek: number; // 0=Sun..6=Sat
  startTime: string; // "HH:MM"
  endTime: string;
  venue?: string | null;
}

interface ScheduledSession {
  sessionIndex: number;
  date: string;
  dayOfWeek: number;
  startTime: string;
  endTime: string;
  venue?: string | null;
  sessionRowId?: number | null;
  topic?: string | null;
}

/**
 * Compute session dates from a timetable definition.
 * Iterates from termStartDate, skips holidays/excludedDates/weekends-not-in-slots,
 * and assigns slots sequentially to sessions.
 */
function computeSchedule(
  totalSessions: number,
  termStartDate: string,
  teachingSlots: TeachingSlot[],
  holidays: string[],
  excludedDates: string[],
): ScheduledSession[] {
  if (!termStartDate || teachingSlots.length === 0 || totalSessions === 0) return [];

  const holidaySet = new Set([...holidays, ...excludedDates]);
  const slotDays = new Set(teachingSlots.map((s) => s.dayOfWeek));

  const sessions: ScheduledSession[] = [];
  const cursor = new Date(termStartDate + "T00:00:00");
  let sessionIndex = 0;

  // Guard: max 365 days to avoid infinite loop
  for (let day = 0; day < 365 && sessions.length < totalSessions; day++) {
    const dayOfWeek = cursor.getDay();
    const dateStr = cursor.toISOString().slice(0, 10);

    if (slotDays.has(dayOfWeek) && !holidaySet.has(dateStr)) {
      // Find all slots for this day, sorted by startTime
      const daySlots = teachingSlots
        .filter((s) => s.dayOfWeek === dayOfWeek)
        .sort((a, b) => a.startTime.localeCompare(b.startTime));

      for (const slot of daySlots) {
        if (sessions.length >= totalSessions) break;
        sessions.push({
          sessionIndex: ++sessionIndex,
          date: dateStr,
          dayOfWeek,
          startTime: slot.startTime,
          endTime: slot.endTime,
          venue: slot.venue ?? null,
          sessionRowId: null,
          topic: null,
        });
      }
    }

    cursor.setDate(cursor.getDate() + 1);
  }

  return sessions;
}

function mapTimetable(t: typeof timetablesTable.$inferSelect) {
  return {
    id: t.id,
    planId: t.planId,
    termStartDate: t.termStartDate,
    termEndDate: t.termEndDate,
    teachingSlots: (t.teachingSlots as TeachingSlot[]) ?? [],
    holidays: (t.holidays as string[]) ?? [],
    excludedDates: (t.excludedDates as string[]) ?? [],
    notes: t.notes,
    createdAt: t.createdAt.toISOString(),
    updatedAt: t.updatedAt.toISOString(),
  };
}

// GET /api/plans/:id/timetable
router.get("/plans/:id/timetable", async (req, res) => {
  try {
    const planId = parseInt(req.params.id);
    const tt = await db.query.timetablesTable.findFirst({
      where: eq(timetablesTable.planId, planId),
    });
    if (!tt) return res.status(404).json({ error: "Timetable not found" });
    return res.json(mapTimetable(tt));
  } catch (err) {
    req.log.error({ err }, "Error getting timetable");
    return res.status(500).json({ error: "Internal server error" });
  }
});

// PUT /api/plans/:id/timetable
router.put("/plans/:id/timetable", async (req, res) => {
  try {
    const { userId } = getAuth(req);
    if (!userId) return res.status(401).json({ error: "Unauthorized" });

    const planId = parseInt(req.params.id);
    const { termStartDate, termEndDate, teachingSlots, holidays, excludedDates, notes } = req.body;

    const existing = await db.query.timetablesTable.findFirst({
      where: eq(timetablesTable.planId, planId),
    });

    let tt;
    if (existing) {
      [tt] = await db
        .update(timetablesTable)
        .set({
          termStartDate: termStartDate ?? existing.termStartDate,
          termEndDate: termEndDate ?? existing.termEndDate,
          teachingSlots: teachingSlots ?? existing.teachingSlots,
          holidays: holidays ?? existing.holidays,
          excludedDates: excludedDates ?? existing.excludedDates,
          notes: notes !== undefined ? notes : existing.notes,
        })
        .where(eq(timetablesTable.planId, planId))
        .returning();
    } else {
      [tt] = await db
        .insert(timetablesTable)
        .values({
          planId,
          termStartDate: termStartDate ?? null,
          termEndDate: termEndDate ?? null,
          teachingSlots: teachingSlots ?? [],
          holidays: holidays ?? [],
          excludedDates: excludedDates ?? [],
          notes: notes ?? null,
        })
        .returning();
    }

    return res.json(mapTimetable(tt));
  } catch (err) {
    req.log.error({ err }, "Error upserting timetable");
    return res.status(500).json({ error: "Internal server error" });
  }
});

// GET /api/plans/:id/timetable/schedule
router.get("/plans/:id/timetable/schedule", async (req, res) => {
  try {
    const planId = parseInt(req.params.id);

    const [tt, sessions] = await Promise.all([
      db.query.timetablesTable.findFirst({ where: eq(timetablesTable.planId, planId) }),
      db.select().from(sessionRowsTable).where(eq(sessionRowsTable.planId, planId)),
    ]);

    if (!tt) return res.json({ totalSessions: 0, sessions: [] });

    const slots = (tt.teachingSlots as TeachingSlot[]) ?? [];
    const holidays = (tt.holidays as string[]) ?? [];
    const excludedDates = (tt.excludedDates as string[]) ?? [];
    const totalSessions = sessions.length;

    const schedule = computeSchedule(
      totalSessions,
      tt.termStartDate ?? "",
      slots,
      holidays,
      excludedDates,
    );

    // Merge session row data into schedule
    const sorted = [...sessions].sort(
      (a, b) => a.weekNumber * 100 + a.sessionNumber - (b.weekNumber * 100 + b.sessionNumber),
    );
    schedule.forEach((s, i) => {
      const row = sorted[i];
      if (row) {
        s.sessionRowId = row.id;
        s.topic = row.topic;
      }
    });

    return res.json({ totalSessions: schedule.length, sessions: schedule });
  } catch (err) {
    req.log.error({ err }, "Error computing schedule");
    return res.status(500).json({ error: "Internal server error" });
  }
});

export default router;
