/**
 * ZKLib TCP/IP poller — connects to ZKTeco devices on the local network
 * using the ZKTeco SDK protocol (default port 4370) and pulls attendance logs.
 *
 * Runs as a BullMQ repeatable job per registered ZKLIB device.
 */
import type { FastifyInstance } from 'fastify';
import { eq } from 'drizzle-orm';
import { devices, students } from '../../db/schema/index.js';
import { processRawScan } from '../attendance/attendance.service.js';

// node-zklib typings are minimal; use dynamic require for compatibility
// eslint-disable-next-line @typescript-eslint/no-require-imports
const ZKLib = require('node-zklib');

export interface ZkPollJobData {
  deviceId: string;
}

export async function pollZkDevice(fastify: FastifyInstance, deviceId: string) {
  const [device] = await fastify.db
    .select()
    .from(devices)
    .where(eq(devices.id, deviceId))
    .limit(1);

  if (!device || !device.deviceIp) {
    fastify.log.warn({ deviceId }, 'ZKLib poll: device not found or missing IP');
    return;
  }

  const zk = new ZKLib(device.deviceIp, device.devicePort ?? 4370, 10000, 4000);

  try {
    await zk.createSocket();
    const { data: logs } = await zk.getAttendances();

    if (!logs || logs.length === 0) {
      await zk.disconnect();
      return;
    }

    const lastSeen = device.lastSeen ? new Date(device.lastSeen) : new Date(0);
    const newLogs = (logs as Array<{ deviceUserId: string; recordTime: Date }>).filter(
      (l) => new Date(l.recordTime) > lastSeen
    );

    fastify.log.info({ deviceId, total: logs.length, new: newLogs.length }, 'ZKLib poll fetched');

    for (const log of newLogs) {
      const [student] = await fastify.db
        .select()
        .from(students)
        .where(eq(students.biometricId, String(log.deviceUserId)))
        .limit(1);

      if (!student) continue;

      await processRawScan(fastify, {
        tenantId: device.tenantId,
        studentId: student.id,
        deviceId: device.id,
        scanTime: new Date(log.recordTime),
      });
    }

    // Update device status and last_seen
    const latestScan = newLogs.reduce<Date | null>((max, l) => {
      const t = new Date(l.recordTime);
      return max === null || t > max ? t : max;
    }, null);

    await fastify.db.update(devices).set({
      status: 'ONLINE',
      lastSeen: latestScan ?? new Date(),
      updatedAt: new Date(),
    }).where(eq(devices.id, deviceId));

    await zk.disconnect();
  } catch (err) {
    fastify.log.error({ err, deviceId }, 'ZKLib poll error');
    await fastify.db.update(devices).set({ status: 'OFFLINE', updatedAt: new Date() }).where(eq(devices.id, deviceId));
    try { await zk.disconnect(); } catch { /* ignore */ }
  }
}
