import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { requireAnyRole, STAFF_SIDE_ROLES } from "@/lib/auth";

/** Teacher-facing attendance-taking sheet — the member counterpart of the
    admin attendance panel. Scoped to the (class, section, subject) pairs of
    the caller's active teaching schedule; GET returns the roster with any
    saved attendance for the date, POST upserts statuses exactly like the
    admin endpoint. */

async function resolveTeacher(institutionId: number, sessionUserId: number) {
  const me = await prisma.user.findUnique({
    where: { id: sessionUserId },
    select: { staffId: true },
  });
  if (!me?.staffId) return null;
  const schedule = await prisma.classSchedule.findMany({
    where: { institutionId, staffId: me.staffId, isActive: true },
    select: { classItemId: true, sectionItemId: true, subjectItemId: true },
  });
  return { staffId: me.staffId, schedule };
}

/** in-scope check: the (class, subject) pair must exist in the teacher's schedule */
function isPairAllowed(
  schedule: { classItemId: number | null; sectionItemId: number | null; subjectItemId: number | null }[],
  classItemId: number,
  subjectItemId: number | null
) {
  return schedule.some(
    (row) =>
      row.classItemId === classItemId &&
      (subjectItemId === null || row.subjectItemId === null || row.subjectItemId === subjectItemId)
  );
}


/** names via the per-institution selections, falling back to the global
    academic item when a schedule references an item outside the selection set */
async function resolveItemNames(institutionId: number, ids: (number | null)[]) {
  const wanted = [...new Set(ids.filter((x): x is number => x != null))];
  const map = new Map<number, { name: string; nameBn: string | null }>();
  if (wanted.length === 0) return map;
  const selections = await prisma.academicSelection.findMany({
    where: { institutionId, academicItemId: { in: wanted } },
    select: { academicItem: { select: { id: true, name: true, nameBn: true } } },
  });
  for (const s of selections) {
    map.set(s.academicItem.id, { name: s.academicItem.name, nameBn: s.academicItem.nameBn });
  }
  const missing = wanted.filter((id) => !map.has(id));
  if (missing.length > 0) {
    const items = await prisma.academicItem.findMany({
      where: { id: { in: missing } },
      select: { id: true, name: true, nameBn: true },
    });
    for (const item of items) {
      map.set(item.id, { name: item.name, nameBn: item.nameBn });
    }
  }
  return map;
}

export async function GET(req: Request) {
  const session = await requireAnyRole([...STAFF_SIDE_ROLES]);
  if (!session || !session.institutionId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  const teacher = await resolveTeacher(session.institutionId, session.userId);
  if (!teacher) return NextResponse.json({ error: "FORBIDDEN" }, { status: 403 });

  const url = new URL(req.url);
  const dateStr = String(url.searchParams.get("date") ?? "").slice(0, 10);
  const date = /^\d{4}-\d{2}-\d{2}$/.test(dateStr) ? new Date(dateStr) : new Date(new Date().toDateString());
  const classItemId = Number(url.searchParams.get("classItemId") ?? "") || null;
  const sectionItemId = Number(url.searchParams.get("sectionItemId") ?? "") || null;
  const shiftItemId = Number(url.searchParams.get("shiftItemId") ?? "") || null;
  const subjectItemId = url.searchParams.get("subjectItemId")
    ? Number(url.searchParams.get("subjectItemId")) || null
    : null;

  const names = await resolveItemNames(
    session.institutionId,
    teacher.schedule.flatMap((r) => [r.classItemId, r.sectionItemId, r.subjectItemId])
  );

  // the institution's shifts — the sitting-time filter, like the admin panel
  const shifts = (
    await prisma.academicSelection.findMany({
      where: { institutionId: session.institutionId, academicItem: { category: "SHIFT" } },
      select: { academicItem: { select: { id: true, name: true, nameBn: true } } },
      orderBy: { academicItem: { id: "asc" } },
    })
  ).map((s) => ({ id: s.academicItem.id, name: s.academicItem.name, nameBn: s.academicItem.nameBn }));

  const seen = new Set<string>();
  const mine = teacher.schedule.map((row) => {
    const key = `${row.classItemId}:${row.sectionItemId}:${row.subjectItemId}`;
    if (seen.has(key)) return null;
    seen.add(key);
    return {
      classItemId: row.classItemId,
      sectionItemId: row.sectionItemId,
      subjectItemId: row.subjectItemId,
      className: row.classItemId ? names.get(row.classItemId)?.name ?? null : null,
      sectionName: row.sectionItemId ? names.get(row.sectionItemId)?.name ?? null : null,
      subjectName: row.subjectItemId ? names.get(row.subjectItemId)?.name ?? null : null,
    };
  }).filter(Boolean);

  if (!classItemId || !isPairAllowed(teacher.schedule, classItemId, subjectItemId)) {
    return NextResponse.json({ date: dateStr, mine, shifts, entries: [], counts: { marked: 0, present: 0, absent: 0, leave: 0 } });
  }

  const rosterWhere = {
    institutionId: session.institutionId,
    status: "ACTIVE" as const,
    classItemId,
    ...(sectionItemId ? { sectionItemId } : {}),
    ...(shiftItemId ? { shiftItemId } : {}),
  };
  const [students, records] = await Promise.all([
    prisma.student.findMany({
      where: rosterWhere,
      orderBy: { roll: "asc" },
      select: { id: true, roll: true, name: true, nameBn: true, photoUrl: true },
    }),
    prisma.studentAttendance.findMany({
      where: {
        institutionId: session.institutionId,
        date,
        subjectItemId,
        student: rosterWhere,
      },
      select: { studentId: true, status: true, inTime: true, outTime: true, source: true },
    }),
  ]);
  const byStudent = new Map(records.map((r) => [r.studentId, r]));

  const counts = { marked: 0, present: 0, absent: 0, leave: 0 };
  const entries = students.map((s) => {
    const rec = byStudent.get(s.id);
    if (rec) {
      counts.marked += 1;
      if (rec.status === "PRESENT") counts.present += 1;
      else if (rec.status === "ABSENT") counts.absent += 1;
      else counts.leave += 1;
    }
    return {
      studentId: s.id,
      roll: s.roll,
      name: s.name,
      nameBn: s.nameBn,
      photoUrl: s.photoUrl,
      status: rec?.status ?? null,
      inTime: rec?.inTime?.toISOString() ?? null,
      outTime: rec?.outTime?.toISOString() ?? null,
      source: rec?.source ?? null,
    };
  });

  return NextResponse.json({
    date: dateStr,
    classItemId,
    subjectItemId,
    mine,
    shifts,
    entries,
    counts,
  });
}

export async function POST(req: Request) {
  const session = await requireAnyRole([...STAFF_SIDE_ROLES]);
  if (!session || !session.institutionId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  const teacher = await resolveTeacher(session.institutionId, session.userId);
  if (!teacher) return NextResponse.json({ error: "FORBIDDEN" }, { status: 403 });

  try {
    const body = await req.json();
    const dateStr = String(body.date ?? "").slice(0, 10);
    if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
      return NextResponse.json({ error: "Invalid date" }, { status: 400 });
    }
    const date = new Date(dateStr);
    const classItemId = Number(body.classItemId);
    const sectionItemId =
      body.sectionItemId !== undefined && body.sectionItemId !== null && body.sectionItemId !== ""
        ? Number(body.sectionItemId)
        : null;
    const shiftItemId =
      body.shiftItemId !== undefined && body.shiftItemId !== null && body.shiftItemId !== ""
        ? Number(body.shiftItemId)
        : null;
    const subjectItemId =
      body.subjectItemId !== undefined && body.subjectItemId !== null && body.subjectItemId !== ""
        ? Number(body.subjectItemId)
        : null;
    const entries: Array<{ studentId: number; status: string }> = Array.isArray(body.entries) ? body.entries : [];
    if (!Number.isInteger(classItemId) || classItemId <= 0) {
      return NextResponse.json({ error: "CLASS_REQUIRED" }, { status: 400 });
    }
    if (!isPairAllowed(teacher.schedule, classItemId, subjectItemId)) {
      return NextResponse.json({ error: "NOT_YOUR_CLASS" }, { status: 403 });
    }
    if (entries.length === 0) {
      return NextResponse.json({ error: "No entries" }, { status: 400 });
    }

    const valid = new Set(["PRESENT", "ABSENT", "LEAVE"]);
    // only ACTIVE students of exactly this class/section/shift can be marked
    const roster = new Set(
      (
        await prisma.student.findMany({
          where: {
            institutionId: session.institutionId,
            status: "ACTIVE",
            classItemId,
            ...(sectionItemId ? { sectionItemId } : {}),
            ...(shiftItemId ? { shiftItemId } : {}),
          },
          select: { id: true },
        })
      ).map((s) => s.id)
    );

    let saved = 0;
    await prisma.$transaction(async (tx) => {
      for (const e of entries) {
        if (!roster.has(Number(e.studentId)) || !valid.has(e.status)) continue;
        const existing = await tx.studentAttendance.findFirst({
          where: { studentId: Number(e.studentId), date, subjectItemId },
          select: { id: true },
        });
        if (existing) {
          await tx.studentAttendance.update({
            where: { id: existing.id },
            data: { status: e.status as "PRESENT" | "ABSENT" | "LEAVE" },
          });
        } else {
          await tx.studentAttendance.create({
            data: {
              institutionId: session.institutionId!,
              studentId: Number(e.studentId),
              date,
              status: e.status as "PRESENT" | "ABSENT" | "LEAVE",
              subjectItemId,
            },
          });
        }
        saved += 1;
      }
    });

    return NextResponse.json({ success: true, saved });
  } catch {
    return NextResponse.json({ error: "Could not save attendance" }, { status: 500 });
  }
}
