import prisma from "../config/prisma.js";
import { Prisma, AttendanceStatus } from "../generated/prisma/client.js";
import { EmployeeRepository } from "./EmployeeRepository.js";

export class AttendanceRepository {
  private employeeRepository = new EmployeeRepository();

  private parseDate(dateStr: string): Date {
    const d = new Date(dateStr);
    return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), 0, 0, 0, 0));
  }

  async getDailyAttendance(dateStr: string, search?: string) {
    const targetDate = this.parseDate(dateStr);

    const empResult = await this.employeeRepository.findAll(undefined, undefined, search, true);
    const employees = empResult.items;

    const empIds = employees.map((e) => e.id);

    const existingAttendances = await prisma.attendance.findMany({
      where: {
        employeeId: { in: empIds },
        date: targetDate,
      },
    });

    const attendanceMap = new Map<string, (typeof existingAttendances)[0]>();
    existingAttendances.forEach((att) => {
      attendanceMap.set(att.employeeId.toString(), att);
    });

    const result = employees.map((emp) => {
      const att = attendanceMap.get(emp.id.toString());
      return {
        employeeId: emp.id.toString(),
        employeeCode: emp.employeeCode,
        name: emp.name,
        mobile: emp.mobile,
        designation: emp.designation,
        status: att ? att.status : null,
        remarks: att ? att.remarks : "",
        isSaved: Boolean(att),
        attendanceId: att ? att.id.toString() : null,
      };
    });

    return result;
  }

  async saveBulkAttendance(
    dateStr: string,
    records: Array<{
      employeeId: string | number;
      status: AttendanceStatus;
      remarks?: string | null;
    }>,
    createdBy?: bigint
  ) {
    const targetDate = this.parseDate(dateStr);

    // Only upsert records that have a valid status set
    const validRecords = records.filter((r) => Boolean(r.status));

    const operations = validRecords.map((rec) => {
      const empId = BigInt(rec.employeeId);
      const status = rec.status;
      const remarks = rec.remarks || null;

      return prisma.attendance.upsert({
        where: {
          employeeId_date: {
            employeeId: empId,
            date: targetDate,
          },
        },
        update: {
          status,
          remarks,
        },
        create: {
          employeeId: empId,
          date: targetDate,
          status,
          remarks,
          createdBy: createdBy ?? null,
        },
      });
    });

    if (operations.length > 0) {
      await prisma.$transaction(operations);
    }
    return this.getSummary(dateStr);
  }

  async getSummary(dateStr: string) {
    const targetDate = this.parseDate(dateStr);

    const activeEmployees = await prisma.employee.count({
      where: { deletedAt: null, status: true },
    });

    const attendances = await prisma.attendance.findMany({
      where: {
        date: targetDate,
        employee: { deletedAt: null, status: true },
      },
    });

    let present = 0;
    let absent = 0;
    let halfDay = 0;

    attendances.forEach((att) => {
      if (att.status === AttendanceStatus.PRESENT) present++;
      else if (att.status === AttendanceStatus.ABSENT) absent++;
      else if (att.status === AttendanceStatus.HALF_DAY) halfDay++;
    });

    const recordedCount = attendances.length;
    const rate = recordedCount > 0 ? Math.round(((present + halfDay * 0.5) / recordedCount) * 100) : 0;

    return {
      date: dateStr,
      totalEmployees: activeEmployees,
      present,
      absent,
      halfDay,
      attendanceRate: rate,
    };
  }

  async getMonthlyAttendance(year: number, month: number, search?: string) {
    const startDate = new Date(Date.UTC(year, month - 1, 1, 0, 0, 0, 0));
    const daysInMonth = new Date(year, month, 0).getDate();
    const endDate = new Date(Date.UTC(year, month - 1, daysInMonth, 23, 59, 59, 999));

    const empResult = await this.employeeRepository.findAll(undefined, undefined, search, true);
    const employees = empResult.items;
    const empIds = employees.map((e) => e.id);

    const attendances = await prisma.attendance.findMany({
      where: {
        employeeId: { in: empIds },
        date: { gte: startDate, lte: endDate },
      },
    });

    // Map: employeeId -> (dayOfMonth -> status)
    const matrix = new Map<string, Map<number, { status: AttendanceStatus; remarks?: string | null }>>();
    attendances.forEach((att) => {
      const empIdStr = att.employeeId.toString();
      if (!matrix.has(empIdStr)) {
        matrix.set(empIdStr, new Map());
      }
      const day = new Date(att.date).getUTCDate();
      matrix.get(empIdStr)!.set(day, { status: att.status, remarks: att.remarks });
    });

    const result = employees.map((emp) => {
      const empMap = matrix.get(emp.id.toString()) || new Map();
      const daily: Record<number, { status: AttendanceStatus | null; remarks?: string | null }> = {};
      let presentCount = 0;
      let absentCount = 0;
      let halfDayCount = 0;

      for (let d = 1; d <= daysInMonth; d++) {
        const rec = empMap.get(d);
        const status = rec ? rec.status : null;
        daily[d] = { status, remarks: rec?.remarks };

        if (status === AttendanceStatus.PRESENT) presentCount++;
        else if (status === AttendanceStatus.ABSENT) absentCount++;
        else if (status === AttendanceStatus.HALF_DAY) halfDayCount++;
      }

      const totalRecorded = presentCount + absentCount + halfDayCount;
      const rate = totalRecorded > 0 ? Math.round(((presentCount + halfDayCount * 0.5) / totalRecorded) * 100) : 0;

      return {
        employeeId: emp.id.toString(),
        employeeCode: emp.employeeCode,
        name: emp.name,
        designation: emp.designation,
        daily,
        stats: {
          presentDays: presentCount,
          absentDays: absentCount,
          halfDays: halfDayCount,
          attendanceRate: rate,
        },
      };
    });

    return {
      year,
      month,
      daysInMonth,
      employees: result,
    };
  }

  async getEmployeeSummary(employeeId: bigint, year?: number, month?: number) {
    const employee = await this.employeeRepository.findById(employeeId);
    if (!employee) return null;

    const where: Prisma.AttendanceWhereInput = { employeeId };

    if (year && month) {
      const startDate = new Date(Date.UTC(year, month - 1, 1));
      const daysInMonth = new Date(year, month, 0).getDate();
      const endDate = new Date(Date.UTC(year, month - 1, daysInMonth, 23, 59, 59, 999));
      where.date = { gte: startDate, lte: endDate };
    }

    const attendances = await prisma.attendance.findMany({
      where,
      orderBy: { id: 'desc' },
    });

    let present = 0;
    let absent = 0;
    let halfDay = 0;

    attendances.forEach((att) => {
      if (att.status === AttendanceStatus.PRESENT) present++;
      else if (att.status === AttendanceStatus.ABSENT) absent++;
      else if (att.status === AttendanceStatus.HALF_DAY) halfDay++;
    });

    const totalRecorded = attendances.length;
    const rate = totalRecorded > 0 ? Math.round(((present + halfDay * 0.5) / totalRecorded) * 100) : 0;

    return {
      employee: {
        id: employee.id.toString(),
        employeeCode: employee.employeeCode,
        name: employee.name,
        mobile: employee.mobile,
        designation: employee.designation,
      },
      stats: {
        totalDays: totalRecorded,
        presentDays: present,
        absentDays: absent,
        halfDays: halfDay,
        attendancePercentage: rate,
      },
      recentLogs: attendances.map((a) => ({
        id: a.id.toString(),
        date: a.date.toISOString().split("T")[0],
        status: a.status,
        remarks: a.remarks,
      })),
    };
  }

  async getReportData(
    startDate?: string,
    endDate?: string,
    employeeId?: bigint,
    status?: AttendanceStatus
  ) {
    const where: Prisma.AttendanceWhereInput = {
      employee: { deletedAt: null },
    };

    if (employeeId) {
      where.employeeId = employeeId;
    }

    if (status) {
      where.status = status;
    }

    if (startDate && endDate) {
      const start = this.parseDate(startDate);
      const end = this.parseDate(endDate);
      end.setUTCHours(23, 59, 59, 999);
      where.date = { gte: start, lte: end };
    } else if (startDate) {
      const start = this.parseDate(startDate);
      where.date = { gte: start };
    }

    const items = await prisma.attendance.findMany({
      where,
      include: {
        employee: true,
      },
      orderBy: [{ date: "desc" }, { employee: { name: "asc" } }],
    });

    return items.map((att) => ({
      id: att.id.toString(),
      date: att.date.toISOString().split("T")[0],
      employeeId: att.employeeId.toString(),
      employeeCode: att.employee.employeeCode,
      employeeName: att.employee.name,
      designation: att.employee.designation,
      status: att.status,
      remarks: att.remarks || "",
    }));
  }
}
