/**
 * ZKTeco BioTime 9.5 API Client
 * NOTE: BioTime 9.5 uses "data" key, NOT "results" like older versions
 * Response shape: { count, next, previous, msg, code, data: [...] }
 */

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

export type ZKEmployee = {
  id: number
  emp_code: string
  first_name: string
  last_name: string
  full_name?: string
  nickname?: string | null
  department: { id: number; dept_code: string; dept_name: string } | null
  position:   { id: number; position_code: string; position_name: string } | null
  hire_date:  string | null
  email:      string | null
  mobile:     string | null
  enable_att: boolean
  gender?:    string | null
  birthday?:  string | null
}

export type ZKTransaction = {
  id: number
  emp_code: string
  employee_name?: string
  employee_department?: string
  punch_time: string        // "2026-05-24 07:29:19"
  punch_state: string       // "0"=check-in "1"=check-out
  verify_type?: number
  terminal_sn: string
  terminal_alias?: string
  area_alias?: string
  is_attendance?: boolean | null
}

export type ZKDepartment = {
  id: number
  dept_code: string
  dept_name: string
  parent_dept: { id: number; dept_code: string; dept_name: string } | null
}

// BioTime 9.5 response shape — uses "data" not "results"
type BioTimeResponse<T> = {
  count: number
  next: string | null
  previous: string | null
  msg: string
  code: number
  data: T[]
}

export class ZKTecoClient {
  private baseUrl: string
  private username: string
  private password: string
  private token: string | null = null
  private tokenExpiry: Date | null = null

  constructor(baseUrl: string, username: string, password: string) {
    this.baseUrl = baseUrl.replace(/\/$/, "")
    this.username = username
    this.password = password
  }

  // ── Auth ────────────────────────────────────────────────────────────────────

  async getToken(): Promise<string> {
    if (this.token && this.tokenExpiry && new Date() < this.tokenExpiry) {
      return this.token
    }

    const res = await fetch(`${this.baseUrl}/jwt-api-token-auth/`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ username: this.username, password: this.password }),
    })

    const text = await res.text()
    if (!res.ok) throw new Error(`ZKTeco auth failed (${res.status}): ${text}`)

    const data = JSON.parse(text)
    if (!data.token) throw new Error(`No token in auth response: ${text.slice(0, 100)}`)

    this.token = data.token as string
    // JWT expires in 7 days per payload, refresh after 6 days
    this.tokenExpiry = new Date(Date.now() + 6 * 24 * 60 * 60 * 1000)
    return this.token
  }

  // ── Core fetch — handles BioTime 9.5 "data" key ──────────────────────────

  private async fetchPage<T>(path: string, params: Record<string, string>): Promise<BioTimeResponse<T>> {
    const token = await this.getToken()
    const url = new URL(`${this.baseUrl}${path}`)
    Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v))

    const res = await fetch(url.toString(), {
      headers: {
        "Content-Type": "application/json",
        "Authorization": `JWT ${token}`,
      },
    })

    const text = await res.text()
    if (!res.ok) throw new Error(`ZKTeco ${res.status} on ${path}: ${text.slice(0, 200)}`)

    const json = JSON.parse(text)

    // BioTime 9.5 uses "data", older versions use "results" — handle both
    if (json.data !== undefined) return json as BioTimeResponse<T>
    if (json.results !== undefined) return { ...json, data: json.results } as BioTimeResponse<T>
    if (Array.isArray(json)) return { count: json.length, next: null, previous: null, msg: "", code: 0, data: json }

    throw new Error(`Unexpected response from ${path}: ${text.slice(0, 200)}`)
  }

  // ── Paginated fetch — fetches ALL pages automatically ────────────────────

  private async fetchAll<T>(path: string, extraParams?: Record<string, string>): Promise<T[]> {
    const all: T[] = []
    let page = 1
    const pageSize = 100

    while (true) {
      const resp = await this.fetchPage<T>(path, {
        page: String(page),
        page_size: String(pageSize),
        ...extraParams,
      })

      all.push(...resp.data)

      // Stop when no next page
      if (!resp.next) break
      page++
    }

    return all
  }

  // ── Public API methods ────────────────────────────────────────────────────

  async getEmployees(): Promise<ZKEmployee[]> {
    return this.fetchAll<ZKEmployee>("/personnel/api/employees/")
  }

  async getDepartments(): Promise<ZKDepartment[]> {
    return this.fetchAll<ZKDepartment>("/personnel/api/departments/")
  }

  async getTransactions(startTime: string, endTime: string): Promise<ZKTransaction[]> {
    return this.fetchAll<ZKTransaction>("/iclock/api/transactions/", {
      start_time: startTime,
      end_time: endTime,
    })
  }

  async getTodayTransactions(): Promise<ZKTransaction[]> {
    const today = new Date().toISOString().split("T")[0]
    return this.getTransactions(`${today} 00:00:00`, `${today} 23:59:59`)
  }

  async getYesterdayTransactions(): Promise<ZKTransaction[]> {
    const d = new Date()
    d.setDate(d.getDate() - 1)
    const day = d.toISOString().split("T")[0]
    return this.getTransactions(`${day} 00:00:00`, `${day} 23:59:59`)
  }

  async getTransactionsForDateRange(fromDate: string, toDate: string): Promise<ZKTransaction[]> {
    return this.getTransactions(`${fromDate} 00:00:00`, `${toDate} 23:59:59`)
  }
}

// ─── Singleton ────────────────────────────────────────────────────────────────

let _client: ZKTecoClient | null = null

export function getZKTecoClient(): ZKTecoClient {
  if (!_client) {
    const baseUrl  = process.env.ZKTECO_BASE_URL
    const username = process.env.ZKTECO_USERNAME
    const password = process.env.ZKTECO_PASSWORD

    if (!baseUrl || !username || !password) {
      throw new Error("Missing env vars: ZKTECO_BASE_URL, ZKTECO_USERNAME, ZKTECO_PASSWORD")
    }
    _client = new ZKTecoClient(baseUrl, username, password)
  }
  return _client
}