/**
 * ZKTeco → HRMS Employee Sync
 * Run: npx tsx src/jobs/sync-employees.ts
 *
 * BioTime hierarchy for your setup:
 *   parent_dept = null           → BRANCH  (e.g. "Main Branch")
 *   parent_dept = {...}          → potential DEPARTMENT
 *     BUT if name is a generic placeholder like "Department" → SKIP IT
 *     Only sync if it has a real meaningful name (IT, HR, Accounting, etc.)
 */

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 { ZKEmployee, ZKDepartment } from "@/lib/zkteco-client"
import type { PrismaClient } from "@prisma/client"

const prisma = db as PrismaClient

type SyncResult = {
  branchesCreated: number
  branchesUpdated: number
  departmentsCreated: number
  departmentsUpdated: number
  employeesCreated: number
  employeesUpdated: number
  errors: string[]
}

// ─── Names that are BioTime placeholders, not real departments ────────────────
// Add more here if needed, all lowercase
const PLACEHOLDER_DEPT_NAMES = new Set([
  "department",
  "departments",
  "default",
  "general",
  "unassigned",
  "none",
  "-",
  "--",
])

function isPlaceholderDept(name: string): boolean {
  return PLACEHOLDER_DEPT_NAMES.has(name.toLowerCase().trim())
}

// ─── Step 1: Top-level BioTime depts → Branch table ──────────────────────────

async function syncBranches(zkDepts: ZKDepartment[]): Promise<{
  created: number
  updated: number
  branchMap: Map<string, string>  // BioTime dept_code → our Branch id
}> {
  let created = 0
  let updated = 0
  const branchMap = new Map<string, string>()

  const topLevel = zkDepts.filter(d => d.parent_dept === null)
  console.log(`[sync] ${topLevel.length} top-level BioTime dept(s) → syncing as Branches`)

  for (const d of topLevel) {
    try {
      // 1. Already linked by zktecoCode?
      let branch = await prisma.branch.findFirst({
        where: { zktecoCode: d.dept_code },
      })

      if (branch) {
        await prisma.branch.update({
          where: { id: branch.id },
          data: { name: d.dept_name, zktecoCode: d.dept_code },
        })
        branchMap.set(d.dept_code, branch.id)
        updated++
        console.log(`[sync]   branch updated: "${d.dept_name}" (code=${d.dept_code})`)
        continue
      }

      // 2. Match by name
      branch = await prisma.branch.findFirst({
        where: { name: { equals: d.dept_name, mode: "insensitive" } },
      })

      if (branch) {
        await prisma.branch.update({
          where: { id: branch.id },
          data: { zktecoCode: d.dept_code },
        })
        branchMap.set(d.dept_code, branch.id)
        updated++
        console.log(`[sync]   branch linked: "${d.dept_name}" → id=${branch.id}`)
        continue
      }

      // 3. Create new Branch
      const newBranch = await prisma.branch.create({
        data: { name: d.dept_name, zktecoCode: d.dept_code },
      })
      branchMap.set(d.dept_code, newBranch.id)
      created++
      console.log(`[sync]   branch created: "${d.dept_name}" id=${newBranch.id}`)
    } catch (err) {
      console.error(`[sync] Branch "${d.dept_name}" error:`, err)
    }
  }

  return { created, updated, branchMap }
}

// ─── Step 2: Child BioTime depts → Department table (skip placeholders) ───────

async function syncDepartments(
  zkDepts: ZKDepartment[],
  branchMap: Map<string, string>
): Promise<{
  created: number
  updated: number
  deptMap: Map<string, string>  // BioTime dept_code → our Department id
}> {
  let created = 0
  let updated = 0
  const deptMap = new Map<string, string>()

  const childDepts = zkDepts.filter(d => d.parent_dept !== null)

  for (const d of childDepts) {
    // ── Skip placeholder names ─────────────────────────────────────────────
    if (isPlaceholderDept(d.dept_name)) {
      console.log(`[sync]   skipped placeholder dept: "${d.dept_name}"`)
      continue
    }

    // Which Branch does this dept belong to?
    const parentEntry = zkDepts.find(x => x.id === d.parent_dept!.id)
    const branchId = parentEntry
      ? (branchMap.get(parentEntry.dept_code) ?? null)
      : null

    if (!branchId) {
      console.warn(`[sync]   dept "${d.dept_name}" — parent branch not found, skipping`)
      continue
    }

    try {
      const existing = await prisma.department.findFirst({
        where: { name: d.dept_name, branchId },
      })

      if (existing) {
        deptMap.set(d.dept_code, existing.id)
        updated++
        console.log(`[sync]   dept exists: "${d.dept_name}" in branch=${branchId}`)
      } else {
        const newDept = await prisma.department.create({
          data: { name: d.dept_name, branchId },
        })
        deptMap.set(d.dept_code, newDept.id)
        created++
        console.log(`[sync]   dept created: "${d.dept_name}" in branch=${branchId}`)
      }
    } catch (err) {
      console.error(`[sync] Dept "${d.dept_name}" error:`, err)
    }
  }

  return { created, updated, deptMap }
}

// ─── Step 3: Employees ────────────────────────────────────────────────────────

async function syncEmployees(
  zkEmployees: ZKEmployee[],
  deptMap:    Map<string, string>,
  branchMap:  Map<string, string>
): Promise<{ created: number; updated: number; errors: string[] }> {
  let created = 0
  let updated = 0
  const errors: string[] = []

  const fallbackBranch = await prisma.branch.findFirst({ orderBy: { createdAt: "asc" } })
  const fallbackBranchId = process.env.ZKTECO_DEFAULT_BRANCH_ID ?? fallbackBranch?.id ?? null

  for (const emp of zkEmployees) {
    if (!emp.enable_att) continue

    try {
      const fullName =
        emp.full_name?.trim() ||
        `${emp.first_name ?? ""} ${emp.last_name ?? ""}`.trim() ||
        emp.emp_code

      const empDeptCode = emp.department?.dept_code ?? null

      // If the employee's BioTime dept is a placeholder → no department assigned
      const empDeptName = emp.department?.dept_name ?? ""
      const deptId = (empDeptCode && !isPlaceholderDept(empDeptName))
        ? (deptMap.get(empDeptCode) ?? null)
        : null

      // Branch: use branchMap if the emp's dept IS a branch node, else fallback
      const branchId =
        (empDeptCode ? branchMap.get(empDeptCode) : null) ?? fallbackBranchId

      const empData = {
        fullName,
        email:        emp.email    ?? null,
        phone:        emp.mobile   ?? null,
        joiningDate:  emp.hire_date ? new Date(emp.hire_date) : null,
        departmentId: deptId,
        branchId:     branchId ?? null,
        zktecoId:     emp.emp_code,
      }

      const existing = await prisma.employee.findFirst({
        where: { zktecoId: emp.emp_code },
      })

      if (existing) {
        await prisma.employee.update({ where: { id: existing.id }, data: empData })
        updated++
        console.log(`[sync]   emp updated: ${fullName} (${emp.emp_code}) dept=${deptId ?? "none"} branch=${branchId}`)
      } else {
        await prisma.employee.create({ data: empData })
        created++
        console.log(`[sync]   emp created: ${fullName} (${emp.emp_code}) dept=${deptId ?? "none"} branch=${branchId}`)
      }
    } catch (err) {
      const msg = `emp_code=${emp.emp_code}: ${err instanceof Error ? err.message : String(err)}`
      errors.push(msg)
      console.error("[sync] ✗", msg)
    }
  }

  return { created, updated, errors }
}

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

export async function runEmployeeSync(): Promise<SyncResult> {
  const result: SyncResult = {
    branchesCreated: 0, branchesUpdated: 0,
    departmentsCreated: 0, departmentsUpdated: 0,
    employeesCreated: 0, employeesUpdated: 0,
    errors: [],
  }

  const client    = getZKTecoClient()
  const startedAt = new Date()

  try {
    console.log("[sync] ── Fetching BioTime departments...")
    const zkDepts = await client.getDepartments()
    console.log(`[sync] Got ${zkDepts.length} BioTime depts:`)
    zkDepts.forEach(d =>
      console.log(`  [${d.dept_code}] "${d.dept_name}"  parent=${d.parent_dept?.dept_name ?? "NONE → branch-level"}`)
    )

    console.log("\n[sync] ── Step 1: Branches")
    const branchSync = await syncBranches(zkDepts)
    result.branchesCreated = branchSync.created
    result.branchesUpdated = branchSync.updated

    console.log("\n[sync] ── Step 2: Departments (skipping placeholders)")
    const deptSync = await syncDepartments(zkDepts, branchSync.branchMap)
    result.departmentsCreated = deptSync.created
    result.departmentsUpdated = deptSync.updated

    console.log("\n[sync] ── Step 3: Employees")
    const zkEmployees = await client.getEmployees()
    console.log(`[sync] Got ${zkEmployees.length} employees`)

    const empSync = await syncEmployees(zkEmployees, deptSync.deptMap, branchSync.branchMap)
    result.employeesCreated = empSync.created
    result.employeesUpdated = empSync.updated
    result.errors.push(...empSync.errors)

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

    console.log(`
[sync] ✓ Complete
  Branches:    created=${result.branchesCreated}  updated=${result.branchesUpdated}
  Departments: created=${result.departmentsCreated}  updated=${result.departmentsUpdated}
  Employees:   created=${result.employeesCreated}  updated=${result.employeesUpdated}
  Errors:      ${result.errors.length}`)
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err)
    result.errors.push(msg)
    console.error("[sync] ✗ Fatal:", msg)
    await prisma.zKSyncLog.create({
      data: {
        syncType: "employees", status: "failed",
        recordsProcessed: 0, recordsCreated: 0, recordsUpdated: 0,
        errors: msg, startedAt, completedAt: new Date(),
      },
    })
  }

  return result
}

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

if (require.main === module) {
  runEmployeeSync()
    .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) })
}