

import { config } from "dotenv"
import { resolve } from "path"
config({ path: resolve(process.cwd(), ".env") })

import { db } from "@/lib/db"
import { getZKTecoClient } from "@/lib/zkteco-client"
import type { ZKTransaction } from "@/lib/zkteco-client"
import type { PrismaClient } from "@prisma/client"

const prisma = db as PrismaClient

// ─── Helpers ──────────────────────────────────────────────────────────────────

function parseZKDate(s: string): Date {
  const offset = process.env.ZKTECO_TIMEZONE_OFFSET ?? "+03:00"
  return new Date(s.replace(" ", "T") + offset)
}

function groupByEmployeeDay(txns: ZKTransaction[]): Map<string, ZKTransaction[]> {
  const map = new Map<string, ZKTransaction[]>()
  for (const t of txns) {
    const date = t.punch_time.split(" ")[0]
    const key = `${t.emp_code}__${date}`
    if (!map.has(key)) map.set(key, [])
    map.get(key)!.push(t)
  }
  return map
}

function extractInOut(punches: ZKTransaction[]) {
  const sorted = [...punches].sort(
    (a, b) => parseZKDate(a.punch_time).getTime() - parseZKDate(b.punch_time).getTime()
  )

  const checkIn = sorted.length > 0 ? parseZKDate(sorted[0].punch_time) : null
  const checkOut = sorted.length > 1 ? parseZKDate(sorted[sorted.length - 1].punch_time) : null
  const totalMinutes = checkIn && checkOut
    ? Math.round((checkOut.getTime() - checkIn.getTime()) / 60000)
    : 0

  let status = "present"
  if (checkIn) {
    const hour = checkIn.getHours()
    const min = checkIn.getMinutes()
    if (hour > 9 || (hour === 9 && min > 30)) status = "late"
  }

  return { checkIn, checkOut, totalMinutes, status }
}

/** Kuwait weekend = Fri(5) + Sat(6). Change if your weekend differs. */
function isWeekend(date: Date): boolean {
  const day = date.getDay()
  return day === 5 || day === 6
}

// ─── Mark absentees for a given date ─────────────────────────────────────────

async function markAbsentees(date: Date): Promise<{ marked: number }> {
  if (isWeekend(date)) {
    console.log(`[sync-attendance] ${date.toISOString().split("T")[0]} is a weekend — skipping absent marking`)
    return { marked: 0 }
  }

  // Skip public holidays
  const holiday = await prisma.publicHoliday.findFirst({
    where: {
      date: { lte: date },
      OR: [{ endDate: null }, { endDate: { gte: date } }],
    },
  })
  if (holiday) {
    console.log(`[sync-attendance] Public holiday (${holiday.name}) — skipping absent marking`)
    return { marked: 0 }
  }

  // All employees that have a zktecoId or userId (i.e. active/trackable)
  const employees = await prisma.employee.findMany({
    where: { OR: [{ zktecoId: { not: null } }, { userId: { not: null } }] },
    select: { id: true, fullName: true, branchId: true },
  })

  let marked = 0
  for (const emp of employees) {
    const existing = await prisma.attendance.findFirst({
      where: { employeeId: emp.id, date },
    })
    if (existing) continue

    await prisma.attendance.create({
      data: {
        employeeId: emp.id,
        branchId: emp.branchId ?? null,
        date,
        checkIn: null,
        checkOut: null,
        status: "absent",
        source: "manual",
        totalMinutes: 0,
        rawPunchCount: 0,
        note: "Auto-marked absent — no BioTime punch recorded",
      },
    })
    marked++
    console.log(`[sync-attendance]   absent: ${emp.fullName}`)
  }

  console.log(`[sync-attendance] Absent marking done — ${marked} employees marked absent`)
  return { marked }
}

// ─── Main ─────────────────────────────────────────────────────────────────────

export async function runAttendanceSync(options?: {
  startTime?: string
  endTime?: string
  markAbsent?: boolean
}): Promise<{
  processed: number
  created: number
  updated: number
  skipped: number
  absent: number
  errors: string[]
}> {
  const result = { processed: 0, created: 0, updated: 0, skipped: 0, absent: 0, errors: [] as string[] }
  const startedAt = new Date()
  const client = getZKTecoClient()

  // Determine which date(s) we're syncing so we can mark absentees for them
  const syncedDates = new Set<string>()

  try {
    let transactions: ZKTransaction[]

    if (options?.startTime && options?.endTime) {
      console.log(`[sync-attendance] Fetching ${options.startTime} → ${options.endTime}`)
      transactions = await client.getTransactions(options.startTime, options.endTime)
    } else {
      console.log("[sync-attendance] Fetching today's transactions...")
      transactions = await client.getTodayTransactions()
    }

    console.log(`[sync-attendance] Got ${transactions.length} punch records`)
    result.processed = transactions.length

    // ── Process punches ──────────────────────────────────────────────────────

    if (transactions.length > 0) {
      const grouped = groupByEmployeeDay(transactions)
      console.log(`[sync-attendance] Processing ${grouped.size} employee-day combinations...`)

      for (const [key, punches] of grouped.entries()) {
        const [empCode, dateStr] = key.split("__")
        syncedDates.add(dateStr)

        try {
          const employee = await prisma.employee.findFirst({
            where: { zktecoId: empCode },
            select: { id: true, branchId: true, fullName: true },
          })

          if (!employee) {
            console.log(`[sync-attendance] ⚠ No employee with zktecoId="${empCode}" — skipped`)
            result.skipped++
            continue
          }

          const date = new Date(dateStr + "T00:00:00.000Z")
          const { checkIn, checkOut, totalMinutes, status } = extractInOut(punches)

          const attendanceData = {
            employeeId: employee.id,
            branchId: employee.branchId ?? null,
            date,
            checkIn,
            checkOut,
            totalMinutes,
            status,
            source: "zkteco",
            rawPunchCount: punches.length,
          }

          const existing = await prisma.attendance.findFirst({
            where: { employeeId: employee.id, date },
          })

          if (existing) {
            await prisma.attendance.update({ where: { id: existing.id }, data: attendanceData })
            result.updated++
          } else {
            await prisma.attendance.create({ data: attendanceData })
            result.created++
          }

          console.log(
            `[sync-attendance]   ${employee.fullName} (${empCode}) ${dateStr} ` +
            `— ${status} in:${checkIn?.toTimeString().slice(0, 5) ?? "?"} out:${checkOut?.toTimeString().slice(0, 5) ?? "?"} (${punches.length} punches)`
          )
        } catch (err) {
          const msg = `${key}: ${err instanceof Error ? err.message : String(err)}`
          result.errors.push(msg)
          console.error("[sync-attendance] ✗", msg)
        }
      }
    }

    // ── Mark absentees for every date that was part of this sync ─────────────
    // If no transactions came back (e.g. holiday with zero punches), still mark
    // yesterday absent so the records are complete.
    if (syncedDates.size === 0) {
      // No punches at all — derive date from options or use yesterday
      if (options?.startTime) {
        const d = options.startTime.split(" ")[0]
        syncedDates.add(d)
      } else {
        const yesterday = new Date()
        yesterday.setDate(yesterday.getDate() - 1)
        syncedDates.add(yesterday.toISOString().split("T")[0])
      }
    }

    const shouldMarkAbsent = options?.markAbsent ?? true
    if (shouldMarkAbsent) {
      console.log(`\n[sync-attendance] Marking absentees for: ${[...syncedDates].join(", ")}`)
      for (const dateStr of syncedDates) {
        const date = new Date(dateStr + "T00:00:00.000Z")
        const { marked } = await markAbsentees(date)
        result.absent += marked
      }
    } else {
      console.log("\n[sync-attendance] markAbsent=false — skipping absent marking")
    }

    await prisma.zKSyncLog.create({
      data: {
        syncType: "attendance",
        status: result.errors.length === 0 ? "success" : "partial",
        recordsProcessed: result.processed,
        recordsCreated: result.created,
        recordsUpdated: result.updated,
        errors: result.errors.length > 0 ? result.errors.join("\n") : null,
        startedAt,
        completedAt: new Date(),
      },
    })

    console.log(
      `[sync-attendance] ✓ Done — present/late: ${result.created + result.updated} | absent: ${result.absent} | skipped: ${result.skipped} | errors: ${result.errors.length}`
    )
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err)
    result.errors.push(msg)
    console.error("[sync-attendance] ✗ Fatal:", msg)

    await prisma.zKSyncLog.create({
      data: {
        syncType: "attendance", status: "failed",
        recordsProcessed: 0, recordsCreated: 0, recordsUpdated: 0,
        errors: msg, startedAt, completedAt: new Date(),
      },
    })
  }

  return result
}

// ─── CLI ──────────────────────────────────────────────────────────────────────

if (require.main === module) {
  const args = process.argv.slice(2).filter((a) => a !== "--")
  const [from, to] = args
  const opts = from && to
    ? { startTime: `${from} 00:00:00`, endTime: `${to} 23:59:59`, markAbsent: false }
    : undefined

  runAttendanceSync(opts)
    .then((r) => {
      console.log("\nResult:", JSON.stringify(r, null, 2))
      process.exit(r.errors.length > 0 ? 1 : 0)
    })
    .catch((e) => { console.error(e); process.exit(1) })
}