import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { requireRole } from "@/lib/auth";
import { PAYMENT_METHODS, TEACHER_DESIGNATIONS } from "@/lib/constants";

type Period = "daily" | "weekly" | "monthly";

/** UTC-day helpers — the system stores dates as UTC-midnight day strings. */
function utcDay(d: Date): Date {
  return new Date(d.toISOString().slice(0, 10) + "T00:00:00.000Z");
}
function addDays(d: Date, days: number): Date {
  const n = new Date(d);
  n.setUTCDate(n.getUTCDate() + days);
  return n;
}
function addMonths(d: Date, months: number): Date {
  const n = new Date(d);
  n.setUTCMonth(n.getUTCMonth() + months);
  return n;
}

/** Period window + trend buckets (inclusive-from / exclusive-to). */
function periodWindow(period: Period) {
  const today = utcDay(new Date());
  let from: Date;
  let to: Date; // exclusive
  let trend: { from: Date; to: Date; label: string }[] = [];

  if (period === "daily") {
    from = today;
    to = addDays(today, 1);
    for (let i = 6; i >= 0; i--) {
      const d = addDays(today, -i);
      trend.push({ from: d, to: addDays(d, 1), label: d.toLocaleString("en", { day: "numeric", month: "short" }) });
    }
  } else if (period === "weekly") {
    // Bangladesh week starts Sunday
    from = addDays(today, -((today.getUTCDay() + 7) % 7));
    to = addDays(from, 7);
    for (let i = 5; i >= 0; i--) {
      const w = addDays(from, -7 * i);
      trend.push({ from: w, to: addDays(w, 7), label: w.toLocaleString("en", { day: "numeric", month: "short" }) });
    }
  } else {
    from = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), 1));
    to = addMonths(from, 1);
    for (let i = 5; i >= 0; i--) {
      const m = addMonths(from, -i);
      trend.push({ from: m, to: addMonths(m, 1), label: m.toLocaleString("en", { month: "short" }) });
    }
  }
  return { from, to, trend, trendFrom: trend[0].from };
}

function rateOf(present: number, absent: number, leave: number): number | null {
  const total = present + absent + leave;
  return total === 0 ? null : Math.round((present / total) * 1000) / 10;
}

export async function GET(req: Request) {
  const session = await requireRole("SUPER_ADMIN");
  if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const url = new URL(req.url);
  const district = url.searchParams.get("district")?.trim() || "";
  const subDistrict = url.searchParams.get("subDistrict")?.trim() || "";
  const periodParam = url.searchParams.get("period") || "monthly";
  const period: Period = (["daily", "weekly", "monthly"] as const).includes(periodParam as Period)
    ? (periodParam as Period)
    : "monthly";

  const { from, to, trend, trendFrom } = periodWindow(period);

  // ---- scope: institutions matching the district / sub-district filter ----
  const where =
    district && subDistrict
      ? { district, subDistrict }
      : district
        ? { district }
        : {};
  const scoped = await prisma.institution.findMany({
    where,
    select: {
      id: true,
      name: true,
      district: true,
      subDistrict: true,
      status: true,
      _count: { select: { students: true, staffs: true, exams: true } },
    },
    orderBy: { name: "asc" },
  });
  const ids = scoped.map((i) => i.id);

  // ---- filter options (district list with counts — always unfiltered) ----
  const allForOptions = await prisma.institution.findMany({
    select: { district: true, subDistrict: true },
  });
  const districtCounts = new Map<string, number>();
  for (const i of allForOptions) {
    const key = i.district || "";
    districtCounts.set(key, (districtCounts.get(key) ?? 0) + 1);
  }
  const subDistrictCounts = new Map<string, number>();
  if (district) {
    for (const i of allForOptions) {
      if (i.district === district && i.subDistrict) {
        subDistrictCounts.set(i.subDistrict, (subDistrictCounts.get(i.subDistrict) ?? 0) + 1);
      }
    }
  }

  if (ids.length === 0) {
    return NextResponse.json({
      period,
      from: from.toISOString(),
      to: to.toISOString(),
      district,
      subDistrict,
      scope: { institutions: 0, students: 0, teachers: 0, districts: 0, subDistricts: 0 },
      rowLevel: district && subDistrict ? "INSTITUTION" : district ? "SUB_DISTRICT" : "DISTRICT",
      rows: [],
      exams: { counts: { total: 0, upcoming: 0, running: 0, finished: 0 }, publishedResults: 0, recent: [] },
      results: [],
      fees: { periodCollected: 0, periodCount: 0, byMethod: [], monthly: [], byInstitution: [] },
      attendance: {
        student: { present: 0, absent: 0, leave: 0, rate: null, trend: [] },
        teacher: { present: 0, absent: 0, leave: 0, rate: null, trend: [] },
        byInstitution: [],
      },
      notices: { activeCount: 0, recent: [] },
      events: { upcomingCount: 0, recent: [] },
      complaints: { counts: { PENDING: 0, IN_REVIEW: 0, RESOLVED: 0, REJECTED: 0 }, openCount: 0, recent: [] },
      options: {
        districts: Array.from(districtCounts.entries())
          .map(([d, count]) => ({ district: d, count }))
          .sort((a, b) => b.count - a.count),
        subDistricts: Array.from(subDistrictCounts.entries())
          .map(([s, count]) => ({ subDistrict: s, count }))
          .sort((a, b) => b.count - a.count),
      },
    });
  }

  // ---- staff: teacher identification + per-institution teacher counts ----
  const staffs = await prisma.staff.findMany({
    where: { institutionId: { in: ids } },
    select: { id: true, institutionId: true, designation: true },
  });
  const teacherIds = new Set(staffs.filter((s) => (TEACHER_DESIGNATIONS as readonly string[]).includes(s.designation)).map((s) => s.id));
  const teachersByInst = new Map<number, number>();
  for (const s of staffs) {
    if (teacherIds.has(s.id)) teachersByInst.set(s.institutionId, (teachersByInst.get(s.institutionId) ?? 0) + 1);
  }

  // ---- attendance (window = trend span so period + trends come from one fetch) ----
  const [studentAtt, staffAtt] = await Promise.all([
    prisma.studentAttendance.findMany({
      where: { institutionId: { in: ids }, date: { gte: trendFrom, lt: to } },
      select: { institutionId: true, date: true, status: true },
    }),
    prisma.staffAttendance.findMany({
      where: { institutionId: { in: ids }, date: { gte: trendFrom, lt: to } },
      select: { institutionId: true, staffId: true, date: true, status: true },
    }),
  ]);
  const teacherAtt = staffAtt.filter((a) => teacherIds.has(a.staffId));

  type AttAgg = { present: number; absent: number; leave: number };
  const emptyAgg = (): AttAgg => ({ present: 0, absent: 0, leave: 0 });
  const addAgg = (agg: AttAgg, status: string) => {
    if (status === "PRESENT") agg.present++;
    else if (status === "ABSENT") agg.absent++;
    else agg.leave++;
  };

  const studentPeriod = emptyAgg();
  const teacherPeriod = emptyAgg();
  const studentByInst = new Map<number, AttAgg>();
  const teacherByInst = new Map<number, AttAgg>();
  const studentTrend = trend.map(() => emptyAgg());
  const teacherTrend = trend.map(() => emptyAgg());

  const bucketOf = (d: Date): number =>
    trend.findIndex((b) => new Date(d).getTime() >= b.from.getTime() && new Date(d).getTime() < b.to.getTime());

  for (const a of studentAtt) {
    let inst = studentByInst.get(a.institutionId);
    if (!inst) studentByInst.set(a.institutionId, (inst = emptyAgg()));
    const bi = bucketOf(a.date);
    const inPeriod = new Date(a.date).getTime() >= from.getTime() && new Date(a.date).getTime() < to.getTime();
    if (inPeriod) {
      addAgg(inst, a.status);
      addAgg(studentPeriod, a.status);
    }
    if (bi >= 0) addAgg(studentTrend[bi], a.status);
  }
  for (const a of teacherAtt) {
    let inst = teacherByInst.get(a.institutionId);
    if (!inst) teacherByInst.set(a.institutionId, (inst = emptyAgg()));
    const bi = bucketOf(a.date);
    const inPeriod = new Date(a.date).getTime() >= from.getTime() && new Date(a.date).getTime() < to.getTime();
    if (inPeriod) {
      addAgg(inst, a.status);
      addAgg(teacherPeriod, a.status);
    }
    if (bi >= 0) addAgg(teacherTrend[bi], a.status);
  }

  // ---- fees (trend span = 6 months for the monthly chart) ----
  const feeFrom = addMonths(utcDay(new Date()), -5);
  feeFrom.setUTCDate(1);
  const feeRows = await prisma.feeCollection.findMany({
    where: { institutionId: { in: ids }, paidAt: { gte: feeFrom, lt: to } },
    select: { institutionId: true, amount: true, method: true, paidAt: true },
  });
  let periodCollected = 0;
  let periodCount = 0;
  const methodAgg = new Map<string, { amount: number; count: number }>();
  const monthlyAgg = new Map<string, number>();
  const feeByInst = new Map<number, { amount: number; count: number }>();
  for (const f of feeRows) {
    const amount = Number(f.amount);
    const t = new Date(f.paidAt).getTime();
    if (t >= from.getTime() && t < to.getTime()) {
      periodCollected += amount;
      periodCount++;
      const m = methodAgg.get(f.method) ?? { amount: 0, count: 0 };
      m.amount += amount;
      m.count++;
      methodAgg.set(f.method, m);
      const fi = feeByInst.get(f.institutionId) ?? { amount: 0, count: 0 };
      fi.amount += amount;
      fi.count++;
      feeByInst.set(f.institutionId, fi);
    }
    const d = new Date(f.paidAt);
    const key = `${d.getUTCFullYear()}-${d.getUTCMonth()}`;
    monthlyAgg.set(key, (monthlyAgg.get(key) ?? 0) + amount);
  }
  const monthly: { key: string; label: string; total: number }[] = [];
  const now = utcDay(new Date());
  for (let i = 5; i >= 0; i--) {
    const d = addMonths(new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)), -i);
    monthly.push({
      key: `${d.getUTCFullYear()}-${d.getUTCMonth()}`,
      label: d.toLocaleString("en", { month: "short" }),
      total: monthlyAgg.get(`${d.getUTCFullYear()}-${d.getUTCMonth()}`) ?? 0,
    });
  }
  const instName = new Map(scoped.map((i) => [i.id, i.name]));

  // ---- exams ----
  const exams = await prisma.exam.findMany({
    where: { institutionId: { in: ids } },
    select: {
      id: true,
      name: true,
      status: true,
      startDate: true,
      endDate: true,
      resultPublished: true,
      schedulePublished: true,
      institutionId: true,
      classItem: { select: { name: true } },
      _count: { select: { schedules: true } },
    },
    orderBy: { startDate: "desc" },
  });
  const examCounts = { total: exams.length, upcoming: 0, running: 0, finished: 0 };
  for (const e of exams) {
    if (e.status === "UPCOMING") examCounts.upcoming++;
    else if (e.status === "RUNNING") examCounts.running++;
    else examCounts.finished++;
  }
  const scopedExamIds = new Set(exams.map((e) => e.id));

  // ---- exam results statistics (per exam, from entered marks) ----
  const marks = await prisma.examMark.findMany({
    where: { institutionId: { in: ids } },
    select: {
      total: true,
      examScheduleId: true,
      examSchedule: { select: { examId: true, fullMarks: true } },
    },
  });
  const perExam = new Map<number, { papers: Set<number>; rows: number; sumPct: number; pctCount: number; highest: number; lowest: number }>();
  for (const m of marks) {
    const ex = m.examSchedule;
    if (!scopedExamIds.has(ex.examId)) continue;
    let agg = perExam.get(ex.examId);
    if (!agg) perExam.set(ex.examId, (agg = { papers: new Set(), rows: 0, sumPct: 0, pctCount: 0, highest: 0, lowest: 101 }));
    agg.papers.add(m.examScheduleId);
    agg.rows++;
    if (m.total != null && ex.fullMarks > 0) {
      const pct = Math.min(100, (m.total / ex.fullMarks) * 100);
      agg.sumPct += pct;
      agg.pctCount++;
      agg.highest = Math.max(agg.highest, pct);
      agg.lowest = Math.min(agg.lowest, pct);
    }
  }
  const results = exams
    .map((e) => {
      const agg = perExam.get(e.id);
      return {
        examId: e.id,
        name: e.name,
        institutionName: instName.get(e.institutionId) ?? "",
        className: e.classItem?.name ?? null,
        status: e.status,
        resultPublished: e.resultPublished,
        marksEntered: agg?.rows ?? 0,
        avgPercent: agg && agg.pctCount > 0 ? Math.round((agg.sumPct / agg.pctCount) * 10) / 10 : null,
        highestPercent: agg && agg.pctCount > 0 ? Math.round(agg.highest * 10) / 10 : null,
        lowestPercent: agg && agg.pctCount > 0 ? Math.round(agg.lowest * 10) / 10 : null,
      };
    })
    .filter((r) => r.marksEntered > 0)
    .sort((a, b) => b.marksEntered - a.marksEntered)
    .slice(0, 10);

  // ---- notices, events, complaints ----
  const today = utcDay(new Date());
  const [noticeAgg, eventAgg, complaintRows] = await Promise.all([
    prisma.notice.aggregate({
      where: { institutionId: { in: ids }, isActive: true, publishDate: { lte: today } },
      _count: true,
    }),
    prisma.eventItem.count({ where: { institutionId: { in: ids }, isActive: true, startDate: { gte: today } } }),
    prisma.complaint.findMany({
      where: { institutionId: { in: ids } },
      orderBy: { createdAt: "desc" },
      take: 200,
      select: { status: true, institutionId: true, subject: true, category: true, complainantType: true, createdAt: true },
    }),
  ]);
  const [noticesRecent, eventsRecent] = await Promise.all([
    prisma.notice.findMany({
      where: { institutionId: { in: ids }, isActive: true },
      orderBy: [{ isPinned: "desc" }, { publishDate: "desc" }],
      take: 8,
      select: {
        id: true, title: true, audience: true, publishDate: true, isPinned: true,
        institution: { select: { name: true } },
      },
    }),
    prisma.eventItem.findMany({
      where: { institutionId: { in: ids }, isActive: true, startDate: { gte: today } },
      orderBy: { startDate: "asc" },
      take: 8,
      select: {
        id: true, title: true, venue: true, category: true, startDate: true, endDate: true,
        institution: { select: { name: true } },
      },
    }),
  ]);
  const complaintCounts = { PENDING: 0, IN_REVIEW: 0, RESOLVED: 0, REJECTED: 0 };
  for (const c of complaintRows) complaintCounts[c.status as keyof typeof complaintCounts]++;
  const complaintsRecent = await prisma.complaint.findMany({
    where: { institutionId: { in: ids } },
    orderBy: { createdAt: "desc" },
    take: 8,
    select: {
      id: true, subject: true, category: true, status: true, complainantType: true, createdAt: true,
      institution: { select: { name: true } },
    },
  });

  // ---- drill-down rows ----
  type Row = {
    key: string;
    label: string;
    institutions: number;
    students: number;
    teachers: number;
    exams: number;
    feesCollected: number;
    openComplaints: number;
    studentRate: number | null;
    teacherRate: number | null;
  };
  const openComplaintsByInst = new Map<number, number>();
  for (const c of complaintRows) {
    if (c.status === "PENDING" || c.status === "IN_REVIEW") {
      openComplaintsByInst.set(c.institutionId, (openComplaintsByInst.get(c.institutionId) ?? 0) + 1);
    }
  }
  const instRow = (id: number, label: string): Row => ({
    key: String(id),
    label,
    institutions: 1,
    students: scoped.find((i) => i.id === id)?._count.students ?? 0,
    teachers: teachersByInst.get(id) ?? 0,
    exams: scoped.find((i) => i.id === id)?._count.exams ?? 0,
    feesCollected: feeByInst.get(id)?.amount ?? 0,
    openComplaints: openComplaintsByInst.get(id) ?? 0,
    studentRate: (() => {
      const a = studentByInst.get(id);
      return a ? rateOf(a.present, a.absent, a.leave) : null;
    })(),
    teacherRate: (() => {
      const a = teacherByInst.get(id);
      return a ? rateOf(a.present, a.absent, a.leave) : null;
    })(),
  });
  const mergeRows = (list: Row[], instIds: number[]): Row => ({
    key: "all",
    label: "",
    institutions: list.reduce((s, r) => s + r.institutions, 0),
    students: list.reduce((s, r) => s + r.students, 0),
    teachers: list.reduce((s, r) => s + r.teachers, 0),
    exams: list.reduce((s, r) => s + r.exams, 0),
    feesCollected: list.reduce((s, r) => s + r.feesCollected, 0),
    openComplaints: list.reduce((s, r) => s + r.openComplaints, 0),
    studentRate: (() => {
      let p = 0, a = 0, l = 0;
      for (const id of instIds) {
        const agg = studentByInst.get(id);
        if (agg) { p += agg.present; a += agg.absent; l += agg.leave; }
      }
      return rateOf(p, a, l);
    })(),
    teacherRate: (() => {
      let p = 0, a = 0, l = 0;
      for (const id of instIds) {
        const agg = teacherByInst.get(id);
        if (agg) { p += agg.present; a += agg.absent; l += agg.leave; }
      }
      return rateOf(p, a, l);
    })(),
  });

  let rows: Row[] = [];
  let rowLevel: "DISTRICT" | "SUB_DISTRICT" | "INSTITUTION" = "DISTRICT";
  if (district && subDistrict) {
    rowLevel = "INSTITUTION";
    rows = scoped.map((i) => instRow(i.id, i.name));
  } else if (district) {
    rowLevel = "SUB_DISTRICT";
    const groups = new Map<string, { list: Row[]; ids: number[] }>();
    for (const i of scoped) {
      const key = i.subDistrict || "";
      if (!groups.has(key)) groups.set(key, { list: [], ids: [] });
      groups.get(key)!.list.push(instRow(i.id, i.name));
      groups.get(key)!.ids.push(i.id);
    }
    rows = Array.from(groups.entries())
      .map(([key, g]) => ({ ...mergeRows(g.list, g.ids), key, label: key }))
      .sort((a, b) => b.institutions - a.institutions || a.label.localeCompare(b.label));
  } else {
    const groups = new Map<string, { list: Row[]; ids: number[] }>();
    for (const i of scoped) {
      const key = i.district || "";
      if (!groups.has(key)) groups.set(key, { list: [], ids: [] });
      groups.get(key)!.list.push(instRow(i.id, i.name));
      groups.get(key)!.ids.push(i.id);
    }
    rows = Array.from(groups.entries())
      .map(([key, g]) => ({ ...mergeRows(g.list, g.ids), key, label: key }))
      .sort((a, b) => b.institutions - a.institutions || a.label.localeCompare(b.label));
  }

  return NextResponse.json({
    period,
    from: from.toISOString(),
    to: to.toISOString(),
    district,
    subDistrict,
    scope: {
      institutions: scoped.length,
      students: scoped.reduce((s, i) => s + i._count.students, 0),
      teachers: Array.from(teachersByInst.values()).reduce((s, n) => s + n, 0),
      districts: new Set(scoped.map((i) => i.district).filter(Boolean)).size,
      subDistricts: new Set(scoped.map((i) => i.subDistrict).filter(Boolean)).size,
    },
    rowLevel,
    rows,
    exams: {
      counts: examCounts,
      publishedResults: exams.filter((e) => e.resultPublished).length,
      recent: exams.slice(0, 12).map((e) => ({
        id: e.id,
        name: e.name,
        institutionName: instName.get(e.institutionId) ?? "",
        className: e.classItem?.name ?? null,
        status: e.status,
        startDate: e.startDate,
        endDate: e.endDate,
        papers: e._count.schedules,
        schedulePublished: e.schedulePublished,
        resultPublished: e.resultPublished,
      })),
    },
    results,
    fees: {
      periodCollected,
      periodCount,
      byMethod: PAYMENT_METHODS.map((m) => ({ method: m, ...(methodAgg.get(m) ?? { amount: 0, count: 0 }) })).filter(
        (m) => m.count > 0
      ),
      monthly,
      byInstitution: Array.from(feeByInst.entries())
        .map(([id, v]) => ({ institutionName: instName.get(id) ?? "", amount: v.amount, count: v.count }))
        .sort((a, b) => b.amount - a.amount)
        .slice(0, 8),
    },
    attendance: {
      student: {
        ...studentPeriod,
        rate: rateOf(studentPeriod.present, studentPeriod.absent, studentPeriod.leave),
        trend: trend.map((b, i) => ({ label: b.label, rate: rateOf(studentTrend[i].present, studentTrend[i].absent, studentTrend[i].leave) })),
      },
      teacher: {
        ...teacherPeriod,
        rate: rateOf(teacherPeriod.present, teacherPeriod.absent, teacherPeriod.leave),
        trend: trend.map((b, i) => ({ label: b.label, rate: rateOf(teacherTrend[i].present, teacherTrend[i].absent, teacherTrend[i].leave) })),
      },
      byInstitution: scoped
        .map((i) => {
          const s = studentByInst.get(i.id);
          const t = teacherByInst.get(i.id);
          return {
            institutionName: i.name,
            studentRate: s ? rateOf(s.present, s.absent, s.leave) : null,
            teacherRate: t ? rateOf(t.present, t.absent, t.leave) : null,
          };
        })
        .sort((a, b) => (b.studentRate ?? -1) - (a.studentRate ?? -1))
        .slice(0, 10),
    },
    notices: { activeCount: noticeAgg._count, recent: noticesRecent },
    events: { upcomingCount: eventAgg, recent: eventsRecent },
    complaints: {
      counts: complaintCounts,
      openCount: complaintCounts.PENDING + complaintCounts.IN_REVIEW,
      recent: complaintsRecent,
    },
    options: {
      districts: Array.from(districtCounts.entries())
        .map(([d, count]) => ({ district: d, count }))
        .sort((a, b) => b.count - a.count),
      subDistricts: Array.from(subDistrictCounts.entries())
        .map(([s, count]) => ({ subDistrict: s, count }))
        .sort((a, b) => b.count - a.count),
    },
  });
}
