const { Admins, RefreshToken, Devices, Employees, EmployeeDevice, RcAccess } = require('../models/Assoc')

const { Op, where, fn, col, literal } = require('sequelize');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const axios = require('axios');
const path = require('path');
const EmployeeLinks = require('../models/EmployeeLinks');
const { v4: uuidv4 } = require('uuid');
const fs = require('fs');
const { verifyDocument } = require("./utils/ocrClient");
const { saveSnapshot } = require('./utils/savesnapshot');
const DocumentVerification = require('../models/DocumentVerification');

exports.login = async (req, res) => {
    try {
        const { email, password, recaptcha } = req.body;
        if (!email || !password) {
            return res.json({ status: 'error', message: 'Email / Password is required' })
        }
        // const secret = '6LcpKxMsAAAAAC9Q9lr-RlukPuyMfI97TZpRgR7q';
        // const googleRes = await axios.post(
        //     `https://www.google.com/recaptcha/api/siteverify`,
        //     {},
        //     {
        //         params: {
        //             secret,
        //             response: recaptcha,
        //         },
        //     }
        // );

        // if (!googleRes.data.success) {
        //     return res.status(400).json({ status: "error", message: "Invalid reCAPTCHA" });
        // }

        const admin = await Admins.findOne({ where: { email: email } })
        if (!admin) {
            return res.json({ status: 'error', message: 'User not found' })
        }
        const compatibleHash = admin.password.replace(/^\$2y\$/, '$2b$');

        const passwordMatch = await bcrypt.compare(password, compatibleHash);

        if (!passwordMatch) {
            return res.json({ status: 'error', message: 'Incorrect password' });
        }
        await admin.update({ lastLoginAt: new Date().toISOString() })

        const jwtData = {
            id: admin.id,
            name: admin.name,
            email: admin.email,
            role: admin.role,
            lastLoginAt: admin.lastLoginAt
        };
        const token = jwt.sign(jwtData, process.env.JWT_SECRET, { algorithm: 'HS256' });
        res.json({ status: 'success', accessToken: token, user: jwtData })
    } catch (error) {
        return res.json({ status: 'error', message: error?.message })
    }
}
exports.getAdmin = async (req, res) => {
    try {
        /* =====================
           AUTH VALIDATION
        ===================== */
        const authHeader = req.headers["authorization"] || req.headers["Authorization"];
        if (!authHeader) {
            return res.json({ status: "error", message: "Authorization header is missing" });
        }

        const token = authHeader.split(" ")[1];
        if (!token) {
            return res.json({ status: "error", message: "Token is missing" });
        }

        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        const userId = decoded.id;

        /* =====================
           FETCH ADMIN
        ===================== */
        const admin = await Admins.findOne({
            where: { id: userId },
            attributes: ["id", "name", "email", "role", "isActive", "lastLoginAt", "createdAt"],
        });

        if (!admin) {
            return res.json({ status: "error", message: "User not found" });
        }

        if (!admin.isActive) {
            return res.json({ status: "error", message: "Account is deactivated" });
        }

        return res.json({ status: "success", user: admin });

    } catch (error) {
        if (error.name === "JsonWebTokenError") {
            return res.json({ status: "error", message: "Invalid token" });
        }
        if (error.name === "TokenExpiredError") {
            return res.json({ status: "error", message: "Token expired" });
        }
        console.error("getAdmin error:", error);
        return res.status(500).json({
            status: "error",
            message: "Server error",
            details: error.message,
        });
    }
};
exports.getDevices = async (req, res) => {
    try {
        /* =====================
           AUTH VALIDATION
        ===================== */
        const authHeader = req.headers["authorization"] || req.headers["Authorization"];
        if (!authHeader) {
            return res.json({ status: "error", message: "Authorization header is missing" });
        }

        const token = authHeader.split(" ")[1];
        if (!token) {
            return res.json({ status: "error", message: "Token is missing" });
        }

        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        const userId = decoded.id;

        /* =====================
           FETCH ADMIN
        ===================== */
        const admin = await Admins.findOne({
            where: { id: userId },
            attributes: ["id", "name", "email", "role", "isActive", "lastLoginAt", "createdAt"],
        });

        if (!admin) {
            return res.json({ status: "error", message: "User not found" });
        }

        if (!admin.isActive) {
            return res.json({ status: "error", message: "Account is deactivated" });
        }
        const devices = await Devices.findAll({
            include: [{
                model: Admins,
                as: 'creator',
                attributes: ["id", "name", "email", "role", "isActive", "lastLoginAt", "createdAt"]
            }]
        });

        return res.json({ status: "success", devices });

    } catch (error) {
        if (error.name === "JsonWebTokenError") {
            return res.json({ status: "error", message: "Invalid token" });
        }
        if (error.name === "TokenExpiredError") {
            return res.json({ status: "error", message: "Token expired" });
        }
        console.error("getAdmin error:", error);
        return res.status(500).json({
            status: "error",
            message: "Server error",
            details: error.message,
        });
    }
}
const getAuthAdmin = async (req) => {
    const authHeader = req.headers["authorization"] || req.headers["Authorization"];
    if (!authHeader) throw Object.assign(new Error("Authorization header is missing"), { type: "auth" });

    const token = authHeader.split(" ")[1];
    if (!token) throw Object.assign(new Error("Token is missing"), { type: "auth" });

    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    const admin = await Admins.findOne({
        where: { id: decoded.id },
        attributes: ["id", "name", "email", "role", "isActive"],
    });

    if (!admin) throw Object.assign(new Error("User not found"), { type: "auth" });
    if (!admin.isActive) throw Object.assign(new Error("Account is deactivated"), { type: "auth" });

    return admin;
};
const handleError = (res, error) => {
    if (error.type === "auth" || error.name === "JsonWebTokenError") {
        return res.json({ status: "error", message: error.message || "Invalid token" });
    }
    if (error.name === "TokenExpiredError") {
        return res.json({ status: "error", message: "Token expired" });
    }
    if (error.code === "ECONNREFUSED" || error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
        return res.json({ status: "error", message: "HeyStar API is unreachable" });
    }
    if (error.response?.status === 502) {
        return res.json({ status: "error", message: "Device is offline or unreachable" });
    }
    console.error("Person error:", error);
    return res.status(500).json({ status: "error", message: "Server error", details: error.message });
};
// const handleError = (res, error) => {
//     if (error.type === "auth" || error.name === "JsonWebTokenError") {
//         return res.json({ status: "error", message: error.message || "Invalid token" });
//     }
//     if (error.name === "TokenExpiredError") {
//         return res.json({ status: "error", message: "Token expired" });
//     }
//     console.error("Device error:", error);
//     return res.status(500).json({ status: "error", message: "Server error", details: error.message });
// };
exports.getDevice = async (req, res) => {
    try {
        await getAuthAdmin(req);

        const { id } = req.query;

        const device = await Devices.findOne({
            where: { id },
            include: [{
                model: Admins,
                as: "creator",
                attributes: ["id", "name", "email", "role"],
            }],
        });

        if (!device) {
            return res.json({ status: "error", message: "Device not found" });
        }

        return res.json({ status: "success", device });
    } catch (error) {
        return handleError(res, error);
    }
};

exports.createDevice = async (req, res) => {
    try {
        const admin = await getAuthAdmin(req);

        if (admin.role !== "superadmin") {
            return res.json({ status: "error", message: "Only super admins can add devices" });
        }

        const { name, location, deviceKey, secret } = req.body;

        if (!name || !deviceKey || !secret) {
            return res.json({ status: "error", message: "name, deviceKey and secret are required" });
        }

        const existing = await Devices.findOne({ where: { deviceKey } });
        if (existing) {
            return res.json({ status: "error", message: "A device with this deviceKey already exists" });
        }

        const device = await Devices.create({
            name,
            location: location || null,
            deviceKey,
            secret,
            isActive: true,
            createdBy: admin.id,
        });

        return res.json({ status: "success", message: "Device created successfully", device });
    } catch (error) {
        return handleError(res, error);
    }
};
exports.editDevice = async (req, res) => {
    try {
        const admin = await getAuthAdmin(req);

        if (admin.role !== "superadmin") {
            return res.json({ status: "error", message: "Only super admins can edit devices" });
        }

        // const { id } = req.params;
        const { id, name, location, secret, isActive } = req.body;

        const device = await Devices.findOne({ where: { id } });
        if (!device) {
            return res.json({ status: "error", message: "Device not found" });
        }

        // Only update fields that were actually sent
        if (name !== undefined) device.name = name;
        if (location !== undefined) device.location = location;
        if (secret !== undefined) device.secret = secret;
        if (isActive !== undefined) device.isActive = isActive;

        await device.save();

        return res.json({ status: "success", message: "Device updated successfully", device });
    } catch (error) {
        return handleError(res, error);
    }
};
exports.deleteDevice = async (req, res) => {
    try {
        const admin = await getAuthAdmin(req);

        if (admin.role !== "superadmin") {
            return res.json({ status: "error", message: "Only super admins can delete devices" });
        }

        const { id } = req.body;

        const device = await Devices.findOne({ where: { id } });
        if (!device) {
            return res.json({ status: "error", message: "Device not found" });
        }

        await device.destroy();

        return res.json({ status: "success", message: "Device deleted successfully" });
    } catch (error) {
        return handleError(res, error);
    }
};
const HEYSTAR_API = "https://heystarapi.logiclane.tech";
exports.getDeviceInfo = async (req, res) => {
    try {
        await getAuthAdmin(req);

        const { id } = req.query;

        // Look up device in our DB to get deviceKey + secret
        const device = await Devices.findOne({ where: { id } });
        if (!device) {
            return res.json({ status: "error", message: "Device not found" });
        }
        if (!device.isActive) {
            return res.json({ status: "error", message: "Device is inactive" });
        }

        // Call HeyStar Spring Boot API
        const response = await axios.post(`${HEYSTAR_API}/api/v1/device/info`, {
            host: "146.190.152.146",
            port: 10011,
            timeout: 15000,
            deviceKey: device.deviceKey,
            secret: device.secret,
        });

        const heyStar = response.data;

        if (!heyStar.success) {
            return res.json({
                status: "error",
                message: heyStar.message || "Device returned an error",
                code: heyStar.code,
            });
        }

        // Update lastSeenAt since device responded
        await device.update({ lastSeenAt: new Date() });

        return res.json({
            status: "success",
            device: {
                id: device.id,
                name: device.name,
                location: device.location,
                deviceKey: device.deviceKey,
                lastSeenAt: device.lastSeenAt,
            },
            info: heyStar.data,
        });

    } catch (error) {
        // HeyStar API unreachable / timeout
        if (error.code === "ECONNREFUSED" || error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
            return res.json({ status: "error", message: "HeyStar API is unreachable" });
        }
        // Device itself timed out (502 from Spring Boot)
        if (error.response?.status === 502) {
            return res.json({ status: "error", message: "Device is offline or unreachable" });
        }
        return handleError(res, error);
    }
};
exports.testDevice = async (req, res) => {
    try {
        await getAuthAdmin(req);

        const { id } = req.query;

        // Look up device in our DB to get deviceKey + secret
        const device = await Devices.findOne({ where: { id } });
        if (!device) {
            return res.json({ status: "error", message: "Device not found" });
        }
        if (!device.isActive) {
            return res.json({ status: "error", message: "Device is inactive" });
        }

        // Call HeyStar Spring Boot API
        const response = await axios.post(`${HEYSTAR_API}/api/v1/device/test`, {
            host: "146.190.152.146",
            port: 10011,
            timeout: 15000,
            deviceKey: device.deviceKey,
            secret: device.secret,
        });

        const heyStar = response.data;

        if (!heyStar.success) {
            return res.json({
                status: "error",
                message: heyStar.message || "Device returned an error",
                code: heyStar.code,
            });
        }

        // Update lastSeenAt since device responded
        await device.update({ lastSeenAt: new Date() });

        return res.json({
            status: "success",
            deviceResponse: heyStar,
            device: {
                id: device.id,
                name: device.name,
                location: device.location,
                deviceKey: device.deviceKey,
                lastSeenAt: device.lastSeenAt,
            },
            info: heyStar.data,
        });

    } catch (error) {
        // HeyStar API unreachable / timeout
        if (error.code === "ECONNREFUSED" || error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
            return res.json({ status: "error", message: "HeyStar API is unreachable" });
        }
        // Device itself timed out (502 from Spring Boot)
        if (error.response?.status === 502) {
            return res.json({ status: "error", message: "Device is offline or unreachable" });
        }
        return handleError(res, error);
    }
};
const getDevice = async (deviceId) => {
    const device = await Devices.findOne({ where: { id: deviceId } });
    if (!device) throw Object.assign(new Error("Device not found"), { type: "auth" });
    if (!device.isActive) throw Object.assign(new Error("Device is inactive"), { type: "auth" });
    return device;
};

exports.personList = async (req, res) => {
    try {
        await getAuthAdmin(req);

        const { deviceId } = req.query;
        if (!deviceId) return res.json({ status: "error", message: "deviceId is required" });

        const device = await getDevice(deviceId);
        let host = '146.190.152.146';
        if (deviceId != '1') {
            host = '146.190.152.146';
        }

        const response = await axios.post(`${HEYSTAR_API}/api/v1/person/list`, {
            host: host,
            port: 10011,
            timeout: 15000,
            deviceKey: device.deviceKey,
            secret: device.secret,
            length: 100
        }, { timeout: 20000 });

        const heyStar = response.data;

        if (!heyStar.success) {
            return res.json({ status: "error", message: heyStar.message || "Device returned an error", code: heyStar.code });
        }

        await device.update({ lastSeenAt: new Date() });

        // data is a flat array
        const list = Array.isArray(heyStar.data) ? heyStar.data : [];

        return res.json({
            status: "success",
            data: list,
            total: list.length,
            device: { id: device.id, name: device.name, location: device.location },
        });

    } catch (error) {
        return handleError(res, error);
    }
};

exports.personFind = async (req, res) => {
    try {
        await getAuthAdmin(req);

        const { deviceId, sn } = req.body;
        console.log("req", req.body)
        if (!deviceId) return res.json({ status: "error", message: "deviceId is required" });
        if (!sn) return res.json({ status: "error", message: "sn (employee number) is required" });

        const device = await getDevice(deviceId);
        console.log("device", device)
        let host = '146.190.152.146';
        if (deviceId != '1') {
            host = '146.190.152.146';
        }
        const payload = {
            host: host,
            port: 10011,
            timeout: 15000,
            deviceKey: device.deviceKey,
            secret: device.secret,
            type: '1',
            key: sn,
        }
        // console.log("device", device)
        console.log("payload", payload)
        const response = await axios.post(`${HEYSTAR_API}/api/v1/person/find`, payload, { timeout: 20000 });

        const heyStar = response.data;

        if (!heyStar.success) {
            return res.json({ status: "error", message: heyStar.message || "Device returned an error", code: heyStar.code });
        }

        await device.update({ lastSeenAt: new Date() });

        return res.json({
            status: "success",
            data: heyStar.data,
            device: { id: device.id, name: device.name, location: device.location },
        });

    } catch (error) {
        return handleError(res, error);
    }
};
exports.addPerson = async (req, res) => {
    try {
        const admin = await getAuthAdmin(req);
        if (admin.role !== "superadmin") throw Object.assign(new Error("Only superadmins can add persons"), { type: "forbidden" });

        const {
            deviceId,
            type = 1,
            sn,
            name,
            cardNo = "",
            idCard = "",
            mobile = "",
            verifyPwd = "",
            voucherCode = "",
            acGroupNumber = 0,
            verifyStyle = 0,
            expiredType = 0,
            validTimeBegin = 0,
            validTimeEnd = 0,
            validCount = 0,
            acTzNumber1 = 1,
            acTzNumber2 = 0,
            acTzNumber3 = 0,
        } = req.body;

        if (!deviceId) return res.json({ status: "error", message: "deviceId is required" });
        if (!sn) return res.json({ status: "error", message: "Employee SN is required" });
        if (!name) return res.json({ status: "error", message: "Name is required" });

        const device = await Devices.findOne({ where: { id: deviceId } });
        if (!device) return res.json({ status: "error", message: "Device not found" });
        if (!device.isActive) return res.json({ status: "error", message: "Device is inactive" });
        let host = "146.190.152.146";
        if (deviceId != '1') {
            host = "146.190.152.146";
        }
        const response = await axios.post(`${HEYSTAR_API}/api/v1/person`, {
            host: host,
            port: 10011,
            timeout: 15000,
            deviceKey: device.deviceKey,
            secret: device.secret,
            type: parseInt(type),
            sn,
            name,
            cardNo,
            // idCard,
            // idCard: /^\d+$/.test(idCard) ? idCard : "",
            mobile,
            verifyPwd,
            voucherCode,
            acGroupNumber: parseInt(acGroupNumber),
            verifyStyle: parseInt(verifyStyle),
            expiredType: parseInt(expiredType),
            // validTimeBegin: parseInt(validTimeBegin),
            // validTimeEnd: parseInt(validTimeEnd),
            // validCount: parseInt(validCount),
            // acTzNumber1: parseInt(acTzNumber1),
            // acTzNumber2: parseInt(acTzNumber2),
            // acTzNumber3: parseInt(acTzNumber3),
        }, { timeout: 20000 });

        const heyStar = response.data;
        if (!heyStar.success) return res.json({ status: "error", message: heyStar.message || "Device returned an error", code: heyStar.code });

        await device.update({ lastSeenAt: new Date() });
        return res.json({ status: "success", data: heyStar.data, message: "Person added successfully" });

    } catch (error) {
        return handleError(res, error);
    }
};
exports.faceFind = async (req, res) => {
    try {
        await getAuthAdmin(req);

        const { deviceId, sn } = req.body;
        console.log("req", req.body)
        if (!deviceId) return res.json({ status: "error", message: "deviceId is required" });
        if (!sn) return res.json({ status: "error", message: "sn (employee number) is required" });

        const device = await getDevice(deviceId);
        console.log("device", device)
        let host = '146.190.152.146';
        if (deviceId != '1') {
            host = '146.190.152.146';
        }
        const payload = {
            host: host,
            port: 10011,
            timeout: 15000,
            deviceKey: device.deviceKey,
            secret: device.secret,
            personSn: sn,
        }
        // console.log("device", device)
        console.log("payload", payload)
        const response = await axios.post(`${HEYSTAR_API}/api/v1/face/find`, payload, { timeout: 20000 });

        const heyStar = response.data;

        if (!heyStar.success) {
            return res.json({ status: "error", message: heyStar.message || "Device returned an error", code: heyStar.code });
        }

        await device.update({ lastSeenAt: new Date() });

        return res.json({
            status: "success",
            data: heyStar.data,
            device: { id: device.id, name: device.name, location: device.location },
        });

    } catch (error) {
        return handleError(res, error);
    }
};
exports.faceAdd = async (req, res) => {
    try {
        const admin = await getAuthAdmin(req);
        if (admin.role !== "superadmin") throw Object.assign(new Error("Only superadmins can update face images"), { type: "forbidden" });

        const { deviceId, personSn, imgBase64, easy = 0 } = req.body;

        if (!deviceId) return res.json({ status: "error", message: "deviceId is required" });
        if (!personSn) return res.json({ status: "error", message: "personSn is required" });
        if (!imgBase64) return res.json({ status: "error", message: "imgBase64 is required" });

        const device = await Devices.findOne({ where: { id: deviceId } });
        if (!device) return res.json({ status: "error", message: "Device not found" });
        if (!device.isActive) return res.json({ status: "error", message: "Device is inactive" });

        // Strip data URI prefix if present
        const cleanBase64 = imgBase64.replace(/^data:image\/\w+;base64,/, "");
        let host = "146.190.152.146";
        if (deviceId != '1') {
            host = "146.190.152.146";
        }
        const response = await axios.post(`${HEYSTAR_API}/api/v1/face`, {
            host: host,
            port: 10011,
            timeout: 15000,
            deviceKey: device.deviceKey,
            secret: device.secret,
            personSn,
            imgBase64: cleanBase64,
            easy: parseInt(easy),
        }, { timeout: 30000 });

        const heyStar = response.data;
        if (!heyStar.success) return res.json({ status: "error", message: heyStar.message || "Device returned an error", code: heyStar.code });

        await device.update({ lastSeenAt: new Date() });
        return res.json({ status: "success", message: "Face image updated successfully", data: heyStar.data });

    } catch (error) {
        return handleError(res, error);
    }
};
exports.recordList = async (req, res) => {
    try {
        await getAuthAdmin(req);

        const { deviceId, personSn, index = 1, length = 20, order = 0 } = req.body;
        if (!deviceId) return res.json({ status: "error", message: "deviceId is required" });

        const device = await getDevice(deviceId);
        let host = '146.190.152.146';
        if (deviceId != '1') {
            host = '146.190.152.146';
        }
        const response = await axios.post(`${HEYSTAR_API}/api/v1/record/list`, {
            host: host,
            port: 10011,
            timeout: 15000,
            deviceKey: device.deviceKey,
            secret: device.secret,
            personSn: personSn || undefined,
            index: parseInt(index),
            length: parseInt(length),
            order: parseInt(order),
        }, { timeout: 20000 });

        const heyStar = response.data;

        if (!heyStar.success) {
            return res.json({ status: "error", message: heyStar.message || "Device returned an error", code: heyStar.code });
        }

        await device.update({ lastSeenAt: new Date() });

        return res.json({
            status: "success",
            data: heyStar.data ?? [],
            device: { id: device.id, name: device.name, location: device.location },
        });

    } catch (error) {
        return handleError(res, error);
    }
};

exports.accessLog = async (req, res) => {
    try {
        await getAuthAdmin(req);

        const {
            deviceId,
            personSn,
            personType = -1,
            recordType = 0,
            startTime = 0,
            endTime = 0,
            index = 1,
            length = 10,
            order = 0,
        } = req.body;

        if (!deviceId) return res.json({ status: "error", message: "deviceId is required" });

        const device = await Devices.findOne({ where: { id: deviceId } });
        if (!device) return res.json({ status: "error", message: "Device not found" });
        if (!device.isActive) return res.json({ status: "error", message: "Device is inactive" });
        let host = "146.190.152.146";
        if (deviceId != '1') {
            host = "146.190.152.146";
        }
        const payload = {
            host: host,
            port: 10011,
            timeout: 15000,
            deviceKey: device.deviceKey,
            secret: device.secret,
            personType: parseInt(personType),
            recordType: parseInt(recordType),
            startTime: parseInt(startTime),
            endTime: parseInt(endTime),
            index: parseInt(index),
            length: parseInt(length),
            order: parseInt(order),
        };

        // Only include personSn if provided
        if (personSn) payload.personSn = personSn;

        const response = await axios.post(`${HEYSTAR_API}/api/v1/record/list`, payload, { timeout: 20000 });

        const heyStar = response.data;

        if (!heyStar.success) {
            return res.json({ status: "error", message: heyStar.message || "Device returned an error", code: heyStar.code });
        }

        await device.update({ lastSeenAt: new Date() });

        return res.json({
            status: "success",
            data: heyStar.data ?? [],
            device: { id: device.id, name: device.name, location: device.location },
        });

    } catch (error) {
        return handleError(res, error);
    }
};

exports.getDeviceConfig = async (req, res) => {
    try {
        await getAuthAdmin(req);
        // const device = await getDevice(req.params.id ?? req.body.deviceId);
        const { deviceId, type = 5 } = req.body;

        const device = await getDevice(deviceId);

        let host = "146.190.152.146";
        if (deviceId != '1') {
            host = "146.190.152.146";
        }
        const response = await axios.post(`${HEYSTAR_API}/api/v1/config/get`, {
            host: host,
            port: 10011,
            timeout: 15000,
            deviceKey: device.deviceKey,
            secret: device.secret,
            type: parseInt(type),
        }, { timeout: 20000 });

        const heyStar = response.data;
        if (!heyStar.success) return res.json({ status: "error", message: heyStar.message, code: heyStar.code });

        await device.update({ lastSeenAt: new Date() });
        return res.json({ status: "success", data: heyStar.data });
    } catch (error) {
        return handleError(res, error);
    }
};

// exports.setDeviceConfig = async (req, res) => {
//     try {
//         await getAuthAdmin(req);
//         const { deviceId } = req.body;
//         const device = await getDevice(deviceId);
//         let host = "146.190.152.146";
//         if (deviceId != '1') {
//             host = "146.190.152.146";
//         }
//         const {
//             sevUploadRecRecordUrl,
//             sevUploadDevHeartbeatUrl,
//             sevUploadAlarmUrl,
//             sevUploadRecStrangerDataEnable,
//             sevUploadDevHeartbeatInterval,
//             sevUploadRecSnapshotEnable,
//         } = req.body;

//         const payload = {
//             host: host,
//             port: 10011,
//             timeout: 15000,
//             deviceKey: device.deviceKey,
//             secret: device.secret,
//         };

//         // Only include fields that were actually sent
//         if (sevUploadRecRecordUrl !== undefined) payload.sevUploadRecRecordUrl = sevUploadRecRecordUrl;
//         if (sevUploadDevHeartbeatUrl !== undefined) payload.sevUploadDevHeartbeatUrl = sevUploadDevHeartbeatUrl;
//         if (sevUploadAlarmUrl !== undefined) payload.sevUploadAlarmUrl = sevUploadAlarmUrl;
//         if (sevUploadRecStrangerDataEnable !== undefined) payload.sevUploadRecStrangerDataEnable = parseInt(sevUploadRecStrangerDataEnable);
//         if (sevUploadDevHeartbeatInterval !== undefined) payload.sevUploadDevHeartbeatInterval = parseInt(sevUploadDevHeartbeatInterval);
//         if (sevUploadRecSnapshotEnable !== undefined) payload.sevUploadRecSnapshotEnable = parseInt(sevUploadRecSnapshotEnable);

//         const response = await axios.post(`${HEYSTAR_API}/api/v1/config/server`, payload, { timeout: 20000 });

//         const heyStar = response.data;
//         if (!heyStar.success) return res.json({ status: "error", message: heyStar.message, code: heyStar.code });

//         await device.update({ lastSeenAt: new Date() });
//         return res.json({ status: "success", data: heyStar.data });
//     } catch (error) {
//         return handleError(res, error);
//     }
// };

exports.setDeviceConfig = async (req, res) => {
    try {
        await getAuthAdmin(req);
        // const device = await getDevice(req.params.id ?? req.body.deviceId);
        const { deviceId } = req.body;

        const device = await getDevice(deviceId);
        let host = "146.190.152.146";
        if (deviceId != '1') {
            host = "146.190.152.146";
        }
        const {
            // Webhook URLs
            sevUploadRecRecordUrl,
            sevUploadDevHeartbeatUrl,
            sevUploadAlarmUrl,
            // Upload toggles
            sevUploadRecStrangerDataEnable,
            sevUploadDevHeartbeatInterval,
            sevUploadRecSnapshotEnable,
            // TCP server connection
            sevConnectIp,
            sevConnectPort,
            sevConnectPassword,
            sevConnectEnable,
        } = req.body;

        const payload = {
            host: host,
            port: 10011,
            timeout: 15000,
            deviceKey: device.deviceKey,
            secret: device.secret,
        };

        // Only include fields that were actually sent
        if (sevUploadRecRecordUrl !== undefined) payload.sevUploadRecRecordUrl = sevUploadRecRecordUrl;
        if (sevUploadDevHeartbeatUrl !== undefined) payload.sevUploadDevHeartbeatUrl = sevUploadDevHeartbeatUrl;
        if (sevUploadAlarmUrl !== undefined) payload.sevUploadAlarmUrl = sevUploadAlarmUrl;
        if (sevUploadRecStrangerDataEnable !== undefined) payload.sevUploadRecStrangerDataEnable = parseInt(sevUploadRecStrangerDataEnable);
        if (sevUploadDevHeartbeatInterval !== undefined) payload.sevUploadDevHeartbeatInterval = parseInt(sevUploadDevHeartbeatInterval);
        if (sevUploadRecSnapshotEnable !== undefined) payload.sevUploadRecSnapshotEnable = parseInt(sevUploadRecSnapshotEnable);
        if (sevConnectIp !== undefined) payload.sevConnectIp = sevConnectIp;
        if (sevConnectPort !== undefined) payload.sevConnectPort = parseInt(sevConnectPort);
        if (sevConnectPassword !== undefined) payload.sevConnectPassword = sevConnectPassword;
        if (sevConnectEnable !== undefined) payload.sevConnectEnable = parseInt(sevConnectEnable);

        const response = await axios.post(`${HEYSTAR_API}/api/v1/config/server`, payload, { timeout: 20000 });

        const heyStar = response.data;
        if (!heyStar.success) return res.json({ status: "error", message: heyStar.message, code: heyStar.code });

        await device.update({ lastSeenAt: new Date() });
        return res.json({ status: "success", data: heyStar.data });
    } catch (error) {
        return handleError(res, error);
    }
};

exports.createEmployeeSignupLink = async (req, res) => {
    try {
        // await getAuthAdmin(req);

        const allDevices = await Devices.findAll({ where: { isActive: true } });
        if (!allDevices.length) return res.json({ status: "error", message: "No active devices found" });

        const uuid = uuidv4();
        const url = `${process.env.SITE_URL}/signup/${uuid}`;

        const createLink = await EmployeeLinks.create({
            device_id: 0,    // 0 = all devices
            signup_token: uuid,
            signup_url: url,
        });

        if (!createLink) return res.json({ status: "error", message: "Error creating link" });
        return res.json({ status: "success", link: url });
    } catch (error) {
        return handleError(res, error);
    }
};

/* =====================
   CHECK LINK
   POST /api/signupCheck
   Public
===================== */
exports.checkLink = async (req, res) => {
    try {
        const { token } = req.body;
        if (!token) return res.json({ status: "error", message: "Token is required" });

        const link = await EmployeeLinks.findOne({ where: { signup_token: token } });
        if (!link) return res.json({ status: "error", message: "This link is invalid or has expired." });
        if (link.signup_name) return res.json({ status: "error", message: "This link has already been used." });

        // Count how many active devices they'll be registered on
        const deviceCount = await Devices.count({ where: { isActive: true } });

        return res.json({
            status: "success",
            deviceCount,
        });
    } catch (error) {
        return handleError(res, error);
    }
};

exports.submitSignup = async (req, res) => {
    try {
        const { token, name, cardNo = "", mobile = "", imgBase64 } = req.body;

        if (!token) return res.json({ status: "error", message: "Token is required" });
        if (!name?.trim()) return res.json({ status: "error", message: "Name is required" });

        const link = await EmployeeLinks.findOne({ where: { signup_token: token } });
        if (!link) return res.json({ status: "error", message: "Invalid link" });
        if (link.signup_name) return res.json({ status: "error", message: "This link has already been used." });

        const allDevices = await Devices.findAll({ where: { isActive: true } });
        if (!allDevices.length) return res.json({ status: "error", message: "No active devices found" });

        const sn = uuidv4().replace(/-/g, "").substring(0, 8).toUpperCase();
        const cleanBase64 = imgBase64 ? imgBase64.replace(/^data:image\/\w+;base64,/, "") : null;
        const results = [];

        for (const device of allDevices) {
            const deviceResult = { deviceId: device.id, deviceName: device.name, personAdded: false, faceAdded: false, error: null };
            let host = "146.190.152.146";
            if (device.id != '1') {
                host = "146.190.152.146";
            }
            try {

                const personRes = await axios.post(`${HEYSTAR_API}/api/v1/person`, {
                    host: host,
                    port: 10011,
                    timeout: 15000,
                    deviceKey: device.deviceKey,
                    secret: device.secret,
                    type: 1,
                    sn,
                    name: name.trim(),
                    cardNo: cardNo.trim(),
                    mobile: mobile.trim(),
                    verifyStyle: 0,
                    expiredType: 0,
                    acTzNumber1: 1,
                }, { timeout: 20000 });

                if (personRes.data.success) {
                    deviceResult.personAdded = true;

                    if (cleanBase64) {
                        try {
                            const faceRes = await axios.post(`${HEYSTAR_API}/api/v1/face`, {
                                host: host,
                                port: 10011,
                                timeout: 15000,
                                deviceKey: device.deviceKey,
                                secret: device.secret,
                                personSn: sn,
                                imgBase64: cleanBase64,
                                easy: 0,
                            }, { timeout: 30000 });

                            deviceResult.faceAdded = faceRes.data.success;
                            if (!faceRes.data.success) console.warn(`Face upload failed on device ${device.id}:`, faceRes.data.message);
                        } catch (faceErr) {
                            console.warn(`Face upload error on device ${device.id}:`, faceErr.message);
                        }
                    }
                } else {
                    deviceResult.error = personRes.data.message;
                    console.warn(`Person add failed on device ${device.id}:`, personRes.data.message);
                }
            } catch (deviceErr) {
                deviceResult.error = deviceErr.message;
                console.warn(`Device ${device.id} unreachable:`, deviceErr.message);
            }

            results.push(deviceResult);
            await device.update({ lastSeenAt: new Date() }).catch(() => { });
        }

        const anySuccess = results.some((r) => r.personAdded);
        if (!anySuccess) {
            const firstError = results.find((r) => r.error)?.error ?? "All devices failed";
            return res.json({ status: "error", message: `Registration failed: ${firstError}` });
        }

        await link.update({
            signup_name: name.trim(),
            signup_image: imgBase64 ? "uploaded" : null,
        });

        return res.json({ status: "success", message: "Registration complete", sn, devices: results });

    } catch (error) {
        return handleError(res, error);
    }
};

/* =====================
   SUBMIT SIGNUP WITH DOC
===================== */
exports.submitSignupWithDoc = async (req, res) => {
    try {
        const {
            token,
            name,
            mobile = "",
            imgBase64,
            docFrontBase64,
            docBackBase64,
            minimumAge = 18,
        } = req.body;

        if (!token) return res.json({ status: "error", message: "Token is required" });
        if (!name?.trim()) return res.json({ status: "error", message: "Name is required" });

        const link = await EmployeeLinks.findOne({ where: { signup_token: token } });
        if (!link) return res.json({ status: "error", message: "Invalid link" });
        if (link.signup_name) return res.json({ status: "error", message: "This link has already been used." });

        const sn = uuidv4().replace(/-/g, "").substring(0, 8).toUpperCase();

        // ── OCR verification ──────────────────────────────────────────
        let docResult = null;
        if (docFrontBase64) {
            try {
                docResult = await verifyDocument({
                    imageBase64: docFrontBase64,
                    backImageBase64: docBackBase64 || null,
                    selfieBase64: imgBase64 || null,
                    personRef: sn,
                    minimumAge,
                });

                if (docResult.is_underage === true) {
                    return res.json({
                        status: "error",
                        message: `Age verification failed — must be ${minimumAge}+`,
                        doc: docResult,
                    });
                }

                // No document number extracted — ask to recapture
                if (!docResult.extracted_document_number) {
                    return res.json({
                        status: "error",
                        code: "DOC_NUMBER_MISSING",
                        message: "Could not read your document number. Please recapture a clearer photo of your ID.",
                        doc: docResult,
                    });
                }

                // Face does not match ID photo — ask to retake selfie
                if (docResult.face_match && docResult.face_match.match === false) {
                    return res.json({
                        status: "error",
                        code: "FACE_MISMATCH",
                        message: "Your selfie does not match the photo on your ID. Please retake your selfie.",
                        doc: docResult,
                    });
                }

                console.log(`[signup] OCR sn=${sn} source=${docResult.source} age=${docResult.age} docNo=${docResult.extracted_document_number}`);
            } catch (ocrErr) {
                console.warn("[signup] OCR service error (non-fatal):", ocrErr.message);
            }
        }

        // ── Register on all active devices ────────────────────────────
        // NOTE: Device registration is handled separately via syncEmployee
        // Uncomment this block if you want to register on signup directly

        const allDevices = await Devices.findAll({ where: { isActive: true } });
        if (!allDevices.length) return res.json({ status: "error", message: "No active devices found" });
        const cleanBase64 = imgBase64 ? imgBase64.replace(/^data:image\/\w+;base64,/, "") : null;
        const results = [];
        for (const device of allDevices) {
            const deviceResult = { deviceId: device.id, deviceName: device.name, personAdded: false, faceAdded: false, error: null };
            let host = "146.190.152.146";
            if (device.id == "1") host = "146.190.152.146";
            try {
                const personRes = await axios.post(`${HEYSTAR_API}/api/v1/person`, {
                    host, port: 10011, timeout: 15000,
                    deviceKey: device.deviceKey, secret: device.secret,
                    type: 1, sn, name: name.trim(), mobile: mobile.trim(),
                    verifyStyle: 0, expiredType: 0, acTzNumber1: 1,
                    cardNo: docResult?.extracted_document_number || "",
                }, { timeout: 20000 });
                if (personRes.data.success) {
                    deviceResult.personAdded = true;
                    if (cleanBase64) {
                        try {
                            const faceRes = await axios.post(`${HEYSTAR_API}/api/v1/face`, {
                                host, port: 10011, timeout: 15000,
                                deviceKey: device.deviceKey, secret: device.secret,
                                personSn: sn, imgBase64: cleanBase64, easy: 1,
                            }, { timeout: 30000 });
                            deviceResult.faceAdded = faceRes.data.success;
                        } catch (fe) { console.warn(`Face error on device ${device.id}:`, fe.message); }
                    }
                } else {
                    deviceResult.error = personRes.data.message;
                }
            } catch (de) {
                deviceResult.error = de.message;
            }
            results.push(deviceResult);
            await device.update({ lastSeenAt: new Date() }).catch(() => { });
        }
        const anySuccess = results.some((r) => r.personAdded);
        if (!anySuccess) {
            const firstError = results.find((r) => r.error)?.error ?? "All devices failed";
            return res.json({ status: "error", message: `Registration failed: ${firstError}` });
        }

        // const results = [];  // empty — no device registration on signup
        const snapshotUrl = await saveSnapshot(imgBase64, sn);

        const checkExisting = await Employees.findOne({ where: { cardNo: docResult?.extracted_document_number } })
        if (checkExisting) {
            return res.json({ status: 'error', message: 'Personnel with card no already exists' })
        }
        // ── Save employee to DB ───────────────────────────────────────
        let employee = null;
        try {
            employee = await Employees.create({
                sn,
                name: name.trim(),
                mobile: mobile.trim() || null,
                cardNo: docResult?.extracted_document_number || null,
                type: 1,
                verifyStyle: 0,
                expiredType: 0,
                acGroupNumber: 0,
                isActive: true,
                faceUrl: snapshotUrl,                                          // no stored URL yet — face was sent directly to device
                documentUrl: docResult?.document_image_url || null,     // front ID photo URL from OCR service
                documentBack: docResult?.document_back_image_url || null,    // back ID photo URL from OCR service
                extractedDob: docResult?.extracted_dob || null,
                docNumber: docResult?.extracted_document_number || null,
                ageVerified: docResult?.is_underage === false ? 1 : (docResult?.is_underage === true ? 0 : null),
                faceMatched: docResult?.face_match?.match === true ? 1 : (docResult?.face_match?.match === false ? 0 : null),
            });
            console.log(`[signup] employee saved id=${employee.id} sn=${sn}`);
        } catch (dbErr) {
            // Non-fatal — person is on device, just log the DB error
            console.warn("[signup] Failed to save employee to DB:", dbErr.message);
        }

        // ── Mark link as used ─────────────────────────────────────────
        await link.update({
            signup_name: name.trim(),
            signup_image: imgBase64 ? "uploaded" : null,
        });

        return res.json({
            status: "success",
            message: "Registration complete — use /api/employee/sync to push to devices",
            sn,
            employeeId: employee?.id ?? null,
            doc: docResult,
        });

    } catch (error) {
        return handleError(res, error);
    }
};
exports.submitCustomerSignupWithDoc = async (req, res) => {
    try {
        const {
            token,
            name = "",
            mobile = "",
            imgBase64,
            docFrontBase64,
            docBackBase64,
            minimumAge = 18,
        } = req.body;

        if (!token) return res.json({ status: "error", message: "Token is required" });
        // if (!name?.trim()) return res.json({ status: "error", message: "Name is required" });

        const link = await EmployeeLinks.findOne({ where: { signup_token: token } });
        if (!link) return res.json({ status: "error", message: "Invalid link" });
        if (link.signup_name) return res.json({ status: "error", message: "This link has already been used." });

        const sn = uuidv4().replace(/-/g, "").substring(0, 8).toUpperCase();

        // ── OCR verification ──────────────────────────────────────────
        let docResult = null;
        if (docFrontBase64) {
            try {
                docResult = await verifyDocument({
                    imageBase64: docFrontBase64,
                    backImageBase64: docBackBase64 || null,
                    selfieBase64: imgBase64 || null,
                    personRef: sn,
                    minimumAge,
                });
                console.log("docResult", docResult)
                if (docResult.is_underage === true) {
                    return res.json({
                        status: "error",
                        message: `Age verification failed — must be ${minimumAge}+`,
                        doc: docResult,
                    });
                }
                if (!docResult?.extracted_name) {
                    return res.json({
                        status: "error",
                        code: "NAME_MISSING",
                        message: "Could not read your name. Please recapture a clearer photo of your ID.",
                        doc: docResult,
                    });
                }
                // No document number extracted — ask to recapture
                if (!docResult.extracted_document_number) {
                    return res.json({
                        status: "error",
                        code: "DOC_NUMBER_MISSING",
                        message: "Could not read your document number. Please recapture a clearer photo of your ID.",
                        doc: docResult,
                    });
                }

                // Face does not match ID photo — ask to retake selfie
                if (docResult.face_match && docResult.face_match.match === false) {
                    return res.json({
                        status: "error",
                        code: "FACE_MISMATCH",
                        message: "Your selfie does not match the photo on your ID. Please retake your selfie.",
                        doc: docResult,
                    });
                }

                console.log(`[signup] OCR sn=${sn} source=${docResult.source} age=${docResult.age} docNo=${docResult.extracted_document_number}`);
            } catch (ocrErr) {
                console.warn("[signup] OCR service error (non-fatal):", ocrErr.message);
            }
        }

        // ── Register on all active devices ────────────────────────────
        // NOTE: Device registration is handled separately via syncEmployee
        // Uncomment this block if you want to register on signup directly

        const allDevices = await Devices.findAll({ where: { isActive: true } });
        if (!allDevices.length) return res.json({ status: "error", message: "No active devices found" });
        const cleanBase64 = imgBase64 ? imgBase64.replace(/^data:image\/\w+;base64,/, "") : null;
        const results = [];
        for (const device of allDevices) {
            const deviceResult = { deviceId: device.id, deviceName: device.name, personAdded: false, faceAdded: false, error: null };
            let host = "146.190.152.146";
            if (device.id == "1") host = "146.190.152.146";
            try {
                const personRes = await axios.post(`${HEYSTAR_API}/api/v1/person`, {
                    host, port: 10011, timeout: 15000,
                    deviceKey: device.deviceKey, secret: device.secret,
                    type: 2, sn, name: docResult?.extracted_name?.trim(), mobile: mobile.trim(),
                    verifyStyle: 0, expiredType: 0, acTzNumber1: 1,
                    cardNo: docResult?.extracted_document_number || "",
                }, { timeout: 20000 });
                if (personRes.data.success) {
                    deviceResult.personAdded = true;
                    if (cleanBase64) {
                        try {
                            const faceRes = await axios.post(`${HEYSTAR_API}/api/v1/face`, {
                                host, port: 10011, timeout: 15000,
                                deviceKey: device.deviceKey, secret: device.secret,
                                personSn: sn, imgBase64: cleanBase64, easy: 1,
                            }, { timeout: 30000 });
                            deviceResult.faceAdded = faceRes.data.success;
                        } catch (fe) { console.warn(`Face error on device ${device.id}:`, fe.message); }
                    }
                } else {
                    deviceResult.error = personRes.data.message;
                }
            } catch (de) {
                deviceResult.error = de.message;
            }
            results.push(deviceResult);
            await device.update({ lastSeenAt: new Date() }).catch(() => { });
        }
        const anySuccess = results.some((r) => r.personAdded);
        if (!anySuccess) {
            const firstError = results.find((r) => r.error)?.error ?? "All devices failed";
            return res.json({ status: "error", message: `Registration failed: ${firstError}` });
        }

        // const results = [];  // empty — no device registration on signup
        const snapshotUrl = await saveSnapshot(imgBase64, sn);

        const checkExisting = await Employees.findOne({ where: { cardNo: docResult?.extracted_document_number } })
        if (checkExisting) {
            return res.json({ status: 'error', message: 'Personnel with card no already exists' })
        }
        // ── Save employee to DB ───────────────────────────────────────
        let employee = null;
        try {
            employee = await Employees.create({
                sn,
                name: docResult?.extracted_name?.trim(),
                mobile: mobile.trim() || null,
                cardNo: docResult?.extracted_document_number || null,
                type: 2,
                verifyStyle: 0,
                expiredType: 0,
                acGroupNumber: 0,
                isActive: true,
                faceUrl: snapshotUrl,                                          // no stored URL yet — face was sent directly to device
                documentUrl: docResult?.document_image_url || null,     // front ID photo URL from OCR service
                documentBack: docResult?.document_back_image_url || null,    // back ID photo URL from OCR service
                extractedDob: docResult?.extracted_dob || null,
                docNumber: docResult?.extracted_document_number || null,
                ageVerified: docResult?.is_underage === false ? 1 : (docResult?.is_underage === true ? 0 : null),
                faceMatched: docResult?.face_match?.match === true ? 1 : (docResult?.face_match?.match === false ? 0 : null),
            });
            console.log(`[signup] employee saved id=${employee.id} sn=${sn}`);
        } catch (dbErr) {
            // Non-fatal — person is on device, just log the DB error
            console.warn("[signup] Failed to save employee to DB:", dbErr.message);
        }

        // ── Mark link as used ─────────────────────────────────────────
        await link.update({
            signup_name: name.trim(),
            signup_image: imgBase64 ? "uploaded" : null,
        });

        return res.json({
            status: "success",
            message: "Registration complete — use /api/employee/sync to push to devices",
            sn,
            employeeId: employee?.id ?? null,
            doc: docResult,
        });

    } catch (error) {
        return handleError(res, error);
    }
};
/* =====================
   SYNC EMPLOYEE TO ALL DEVICES
   POST /api/employee/sync
   Body: { employeeId } or { sn }
   SuperAdmin only
===================== */
exports.syncEmployee = async (req, res) => {
    try {
        const { employeeId, sn: snParam } = req.body;

        // Find employee
        const where = employeeId ? { id: employeeId } : { sn: snParam };
        const employee = await Employees.findOne({ where });
        if (!employee) return res.json({ status: "error", message: "Employee not found" });

        const allDevices = await Devices.findAll({ where: { isActive: true } });
        if (!allDevices.length) return res.json({ status: "error", message: "No active devices found" });

        const results = [];

        for (const device of allDevices) {
            const deviceResult = { deviceId: device.id, deviceName: device.name, success: false, faceAdded: false, error: null };
            let host = "146.190.152.146";
            if (device.id == "1") host = "72.62.131.57";

            try {
                // Add/update person on device
                const personRes = await axios.post(`${HEYSTAR_API}/api/v1/person`, {
                    host, port: 10011, timeout: 15000,
                    deviceKey: device.deviceKey,
                    secret: device.secret,
                    type: employee.type,
                    sn: employee.sn,
                    name: employee.name,
                    mobile: employee.mobile || "",
                    cardNo: employee.cardNo || "",
                    verifyStyle: employee.verifyStyle,
                    expiredType: employee.expiredType,
                    acGroupNumber: employee.acGroupNumber,
                    acTzNumber1: 1,
                    validTimeBegin: employee.validTimeBegin || 0,
                    validTimeEnd: employee.validTimeEnd || 0,
                }, { timeout: 20000 });

                if (personRes.data.success) {
                    deviceResult.success = true;

                    // Sync face if faceUrl is stored
                    if (employee.faceUrl) {
                        try {
                            // Fetch image and convert to base64
                            const imgRes = await axios.get(employee.faceUrl, { responseType: "arraybuffer", timeout: 15000 });
                            const b64 = Buffer.from(imgRes.data).toString("base64");
                            const faceRes = await axios.post(`${HEYSTAR_API}/api/v1/face`, {
                                host, port: 10011, timeout: 15000,
                                deviceKey: device.deviceKey,
                                secret: device.secret,
                                personSn: employee.sn,
                                imgBase64: b64,
                                easy: 1,
                            }, { timeout: 30000 });
                            deviceResult.faceAdded = faceRes.data.success;
                        } catch (fe) {
                            console.warn(`[sync] Face upload failed on device ${device.id}:`, fe.message);
                        }
                    }
                } else {
                    deviceResult.error = personRes.data.message;
                }
            } catch (de) {
                deviceResult.error = de.message;
            }

            results.push(deviceResult);
            await device.update({ lastSeenAt: new Date() }).catch(() => { });
        }

        const anySuccess = results.some((r) => r.success);

        return res.json({
            status: anySuccess ? "success" : "error",
            message: anySuccess ? `Employee ${employee.name} synced to ${results.filter(r => r.success).length}/${allDevices.length} devices` : "Sync failed on all devices",
            employee: { id: employee.id, sn: employee.sn, name: employee.name },
            devices: results,
        });

    } catch (error) {
        return handleError(res, error);
    }
};

/* =====================
   SYNC ALL EMPLOYEES TO ALL DEVICES
   POST /api/employee/syncAll
   SuperAdmin only
===================== */
exports.syncAllEmployees = async (req, res) => {
    try {
        const admin = await getAuthAdmin(req);
        if (admin.role !== "superadmin") {
            return res.json({ status: "error", message: "Only superadmins can sync employees" });
        }

        const allDevices = await Devices.findAll({ where: { isActive: true } });
        const allEmployees = await Employees.findAll({ where: { isActive: true } });

        if (!allDevices.length) return res.json({ status: "error", message: "No active devices found" });
        if (!allEmployees.length) return res.json({ status: "error", message: "No active employees found" });

        let synced = 0;
        let failed = 0;

        for (const employee of allEmployees) {
            let employeeSynced = false;

            for (const device of allDevices) {
                let host = "146.190.152.146";
                if (device.id == "1") host = "146.190.152.146";

                try {
                    const personRes = await axios.post(`${HEYSTAR_API}/api/v1/person`, {
                        host, port: 10011, timeout: 15000,
                        deviceKey: device.deviceKey,
                        secret: device.secret,
                        type: employee.type,
                        sn: employee.sn,
                        name: employee.name,
                        mobile: employee.mobile || "",
                        cardNo: employee.cardNo || "",
                        verifyStyle: employee.verifyStyle,
                        expiredType: employee.expiredType,
                        acGroupNumber: employee.acGroupNumber,
                        acTzNumber1: 1,
                        // validTimeBegin: employee.validTimeBegin || 0,
                        // validTimeEnd: employee.validTimeEnd || 0,
                    }, { timeout: 20000 });

                    if (personRes.data.success) {
                        employeeSynced = true;
                        console.log(`[syncAll] Person registered for ${employee.sn} on device ${device.id}:`);

                        // Sync face if faceUrl available
                        if (employee.faceUrl) {
                            try {
                                const imgRes = await axios.get(employee.faceUrl, { responseType: "arraybuffer", timeout: 15000 });
                                const b64 = Buffer.from(imgRes.data).toString("base64");
                                await axios.post(`${HEYSTAR_API}/api/v1/face`, {
                                    host, port: 10011, timeout: 15000,
                                    deviceKey: device.deviceKey,
                                    secret: device.secret,
                                    personSn: employee.sn,
                                    imgBase64: b64,
                                    easy: 1,
                                }, { timeout: 30000 });
                                console.log(`[syncAll] Face registered for ${employee.sn} on device ${device.id}:`);

                            } catch (fe) {
                                console.warn(`[syncAll] Face failed for ${employee.sn} on device ${device.id}:`, fe.message);
                            }
                        }
                    } else {
                        console.log(`[syncAll] Person failed for registration ${employee.sn} on device ${device.id}:`, personRes?.data);

                    }
                } catch (de) {
                    console.warn(`[syncAll] Device ${device.id} failed for ${employee.sn}:`, de.message);
                }

                await device.update({ lastSeenAt: new Date() }).catch(() => { });
            }

            if (employeeSynced) synced++;
            else failed++;
        }

        return res.json({
            status: "success",
            message: `Sync complete`,
            synced,
            failed,
            total: allEmployees.length,
        });

    } catch (error) {
        return handleError(res, error);
    }
};

// exports.submitSignupWithDoc = async (req, res) => {
//     try {
//         const {
//             token,
//             name,
//             mobile = "",
//             imgBase64,
//             docFrontBase64,
//             docBackBase64,
//             minimumAge = 18,
//         } = req.body;

//         if (!token) return res.json({ status: "error", message: "Token is required" });
//         if (!name?.trim()) return res.json({ status: "error", message: "Name is required" });

//         const link = await EmployeeLinks.findOne({ where: { signup_token: token } });
//         if (!link) return res.json({ status: "error", message: "Invalid link" });
//         if (link.signup_name) return res.json({ status: "error", message: "This link has already been used." });

//         // Generate SN first so we can use it as person_ref for OCR
//         const sn = uuidv4().replace(/-/g, "").substring(0, 8).toUpperCase();

//         // ── Document OCR verification ─────────────────────────────────
//         let docResult = null;
//         if (docFrontBase64) {
//             try {
//                 docResult = await verifyDocument({
//                     imageBase64: docFrontBase64,
//                     backImageBase64: docBackBase64 || null,
//                     selfieBase64: imgBase64 || null,
//                     personRef: sn,
//                     minimumAge,
//                 });

//                 if (docResult.is_underage === true) {
//                     return res.json({
//                         status: "error",
//                         message: `Age verification failed — must be ${minimumAge}+`,
//                         doc: docResult,
//                     });
//                 }

//                 console.log(`[signup] OCR sn=${sn} source=${docResult.source} age=${docResult.age} underage=${docResult.is_underage}`);
//             } catch (ocrErr) {
//                 console.warn("[signup] OCR service error (non-fatal):", ocrErr.message);
//             }
//         }

//         // ── Register on all active devices ────────────────────────────
//         const allDevices = await Devices.findAll({ where: { isActive: true } });
//         if (!allDevices.length) return res.json({ status: "error", message: "No active devices found" });

//         const cleanBase64 = imgBase64 ? imgBase64.replace(/^data:image\/\w+;base64,/, "") : null;
//         console.log(`[signup] sn=${sn} name=${name} hasface=${!!cleanBase64} hasdoc=${!!docFrontBase64}`);

//         const results = [];

//         for (const device of allDevices) {
//             const deviceResult = { deviceId: device.id, deviceName: device.name, personAdded: false, faceAdded: false, error: null };
//             let host = "146.190.152.146";
//             if (device.id != "1") host = "146.190.152.146";

//             try {
//                 const personRes = await axios.post(`${HEYSTAR_API}/api/v1/person`, {
//                     host, port: 10011, timeout: 15000,
//                     deviceKey: device.deviceKey, secret: device.secret,
//                     type: 1, sn, name: name.trim(), mobile: mobile.trim(),
//                     verifyStyle: 0, expiredType: 0, acTzNumber1: 1, cardNo: docResult?.extracted_document_number
//                 }, { timeout: 20000 });

//                 if (personRes.data.success) {
//                     deviceResult.personAdded = true;

//                     if (cleanBase64) {
//                         console.log(`[signup] uploading face sn=${sn} device=${device.id} len=${cleanBase64.length}`);
//                         try {
//                             const faceRes = await axios.post(`${HEYSTAR_API}/api/v1/face`, {
//                                 host, port: 10011, timeout: 15000,
//                                 deviceKey: device.deviceKey, secret: device.secret,
//                                 personSn: sn, imgBase64: cleanBase64, easy: 1,
//                             }, { timeout: 30000 });
//                             deviceResult.faceAdded = faceRes.data.success;
//                             console.log(`[signup] face result device ${device.id}:`, JSON.stringify(faceRes.data));
//                             if (!faceRes.data.success) console.warn(`Face failed on device ${device.id}:`, faceRes.data.message);
//                         } catch (fe) {
//                             console.warn(`Face error on device ${device.id}:`, fe.message);
//                         }
//                     } else {
//                         console.log(`[signup] no face — imgBase64 was empty for sn=${sn}`);
//                     }
//                 } else {
//                     deviceResult.error = personRes.data.message;
//                     console.warn(`Person add failed on device ${device.id}:`, personRes.data.message);
//                 }
//             } catch (de) {
//                 deviceResult.error = de.message;
//                 console.warn(`Device ${device.id} unreachable:`, de.message);
//             }

//             results.push(deviceResult);
//             await device.update({ lastSeenAt: new Date() }).catch(() => { });
//         }

//         const anySuccess = results.some((r) => r.personAdded);
//         if (!anySuccess) {
//             const firstError = results.find((r) => r.error)?.error ?? "All devices failed";
//             return res.json({ status: "error", message: `Registration failed: ${firstError}` });
//         }

//         await link.update({
//             signup_name: name.trim(),
//             signup_image: imgBase64 ? "uploaded" : null,
//         });

//         return res.json({ status: "success", message: "Registration complete", sn, devices: results, doc: docResult });

//     } catch (error) {
//         return handleError(res, error);
//     }
// };
exports.listPeople = async (req, res) => {
    try {
        await getAuthAdmin(req);

        const {
            search = "",
            page = 1,
            limit = 20,
            type,        // 1=Employee 2=Visitor 3=Blacklist
            isActive,    // "true" / "false"
        } = req.query;
        const { sortField = "createdAt", sortOrder = "DESC" } = req.query;
        const pageNum = Math.max(1, parseInt(page));
        const limitNum = Math.min(100, Math.max(1, parseInt(limit)));
        const offset = (pageNum - 1) * limitNum;

        // ── Build where clause ────────────────────────────────────────
        const where = {};

        if (search.trim()) {
            where[Op.or] = [
                { name: { [Op.like]: `%${search.trim()}%` } },
                { sn: { [Op.like]: `%${search.trim()}%` } },
                { mobile: { [Op.like]: `%${search.trim()}%` } },
                { cardNo: { [Op.like]: `%${search.trim()}%` } },
            ];
        }

        // if (type !== undefined) where.type = parseInt(type);
        if (type !== undefined && type !== "" && !isNaN(type)) {
            where.type = parseInt(type);
        }
        // if (isActive !== undefined) where.isActive = isActive === "true";
        if (isActive === "true" || isActive === "false") {
            where.isActive = isActive === "true";
        }
        const order = [[sortField, sortOrder.toUpperCase() === "ASC" ? "ASC" : "DESC"]];
        // ── Query ─────────────────────────────────────────────────────
        const { count, rows } = await Employees.findAndCountAll({
            where,
            order,
            limit: limitNum,
            offset,
        });

        return res.json({
            status: "success",
            employees: rows,
            pagination: {
                total: count,
                page: pageNum,
                limit: limitNum,
                totalPages: Math.ceil(count / limitNum),
                hasNext: pageNum < Math.ceil(count / limitNum),
                hasPrev: pageNum > 1,
            },
        });

    } catch (error) {
        return handleError(res, error);
    }
};
exports.viewPerson = async (req, res) => {
    try {
        await getAuthAdmin(req);

        const { id } = req.body;

        if (!id) {
            return res.json({ status: "error", message: "Person ID required" });
        }

        // ─────────────────────────────
        // Get person
        // ─────────────────────────────
        const person = await Employees.findOne({
            where: { id },
        });

        if (!person) {
            return res.json({ status: "error", message: "Person not found" });
        }

        // ─────────────────────────────
        // Get OCR verification using SN
        // ─────────────────────────────
        let verification = null;

        if (person.sn) {
            verification = await DocumentVerification.findOne({
                where: { person_ref: person.sn },
                order: [["created_at", "DESC"]],
            });
        }

        return res.json({
            status: "success",
            person,
            verification,
        });

    } catch (error) {
        return handleError(res, error);
    }
};
exports.deletePerson = async (req, res) => {
    try {
        const admin = await getAuthAdmin(req);
        if (admin.role !== "superadmin") throw Object.assign(new Error("Only superadmins can delete persons"), { type: "forbidden" });

        const { sn } = req.body;

        if (!sn || (Array.isArray(sn) && sn.length === 0)) {
            return res.json({ status: "error", message: "sn is required" });
        }

        const snArray = Array.isArray(sn) ? sn : [sn];

        const allDevices = await Devices.findAll({ where: { isActive: true } });
        if (!allDevices.length) return res.json({ status: "error", message: "No active devices found" });

        const results = [];

        for (const device of allDevices) {
            const deviceResult = {
                deviceId: device.id,
                deviceName: device.name,
                success: false,
                error: null,
            };

            try {
                const response = await axios.delete(`${HEYSTAR_API}/api/v1/person`, {
                    data: {
                        host: "146.190.152.146",
                        port: 10011,
                        timeout: 15000,
                        deviceKey: device.deviceKey,
                        secret: device.secret,
                        sn: snArray,
                    },
                    timeout: 20000,
                });

                const heyStar = response.data;
                if (heyStar.success) {
                    deviceResult.success = true;
                } else {
                    deviceResult.error = heyStar.message || "Device returned an error";
                    console.warn(`[deletePerson] failed on device ${device.id}: ${deviceResult.error}`);
                }
            } catch (e) {
                deviceResult.error = e.message;
                console.warn(`[deletePerson] error on device ${device.id}:`, e.message);
            }

            results.push(deviceResult);
            await device.update({ lastSeenAt: new Date() }).catch(() => { });
        }

        const anySuccess = results.some((r) => r.success);
        const allSuccess = results.every((r) => r.success);

        return res.json({
            status: anySuccess ? "success" : "error",
            message: allSuccess
                ? `Deleted ${snArray.length} person(s) from all ${allDevices.length} devices`
                : anySuccess
                    ? `Deleted from some devices — check results for details`
                    : "Failed to delete from all devices",
            sn: snArray,
            devices: results,
        });

    } catch (error) {
        return handleError(res, error);
    }
};
exports.listReviews = async (req, res) => {
    try {
        await getAuthAdmin(req);
        const { page = 1, limit = 20 } = req.query;
        const pageNum = Math.max(1, parseInt(page));
        const limitNum = Math.min(100, Math.max(1, parseInt(limit)));
        const offset = (pageNum - 1) * limitNum;

        const { count, rows } = await Employees.findAndCountAll({
            where: {
                [Op.or]: [
                    { ageVerified: false },
                    { ageVerified: null },
                ],
            },
            order: [["createdAt", "DESC"]],
            limit: limitNum,
            offset,
        });

        return res.json({
            status: "success",
            reviews: rows,
            pagination: {
                total: count,
                page: pageNum,
                limit: limitNum,
                totalPages: Math.ceil(count / limitNum),
                hasNext: pageNum < Math.ceil(count / limitNum),
                hasPrev: pageNum > 1,
            },
        });
    } catch (error) {
        return handleError(res, error);
    }
};
exports.reviewPerson = async (req, res) => {
    try {
        const admin = await getAuthAdmin(req);
        if (admin.role !== "superadmin") {
            return res.json({ status: "error", message: "Only superadmins can review registrations" });
        }

        const { id, action } = req.body;

        if (!id) return res.json({ status: "error", message: "Employee ID is required" });
        if (!["approve", "reject"].includes(action)) return res.json({ status: "error", message: "Action must be 'approve' or 'reject'" });

        const employee = await Employees.findOne({ where: { id } });
        if (!employee) return res.json({ status: "error", message: "Employee not found" });

        const ageVerified = action === "approve" ? true : false;

        await employee.update({ ageVerified });

        console.log(`[review] employee id=${id} sn=${employee.sn} action=${action} by admin=${admin.id}`);

        return res.json({
            status: "success",
            message: action === "approve"
                ? `${employee.name} has been approved — age verified`
                : `${employee.name} has been rejected`,
            ageVerified,
            employee: {
                id: employee.id,
                sn: employee.sn,
                name: employee.name,
            },
        });

    } catch (error) {
        return handleError(res, error);
    }
};
// exports.submitSignupWithDoc = async (req, res) => {
//     try {
//         const {
//             token,
//             name,
//             mobile = "",
//             imgBase64,
//             docFrontBase64,
//             docBackBase64,
//             minimumAge = 18,
//         } = req.body;

//         if (!token) return res.json({ status: "error", message: "Token is required" });
//         if (!name?.trim()) return res.json({ status: "error", message: "Name is required" });

//         const link = await EmployeeLinks.findOne({ where: { signup_token: token } });
//         if (!link) return res.json({ status: "error", message: "Invalid link" });
//         if (link.signup_name) return res.json({ status: "error", message: "This link has already been used." });

//         // Generate SN first so we can use it as person_ref for OCR
//         const sn = uuidv4().replace(/-/g, "").substring(0, 8).toUpperCase();

//         // ── Document OCR verification ─────────────────────────────────
//         let docResult = null;
//         if (docFrontBase64) {
//             try {
//                 docResult = await verifyDocument({
//                     imageBase64: docFrontBase64,
//                     backImageBase64: docBackBase64 || null,
//                     personRef: sn,              // use generated SN — no frontend input needed
//                     minimumAge,
//                 });

//                 if (docResult.is_underage === true) {
//                     return res.json({
//                         status: "error",
//                         message: `Age verification failed — must be ${minimumAge}+`,
//                         doc: docResult,
//                     });
//                 }

//                 console.log(`[signup] OCR sn=${sn} source=${docResult.source} age=${docResult.age} underage=${docResult.is_underage}`);
//             } catch (ocrErr) {
//                 console.warn("[signup] OCR service error (non-fatal):", ocrErr.message);
//             }
//         }

//         // ── Register on all active devices ────────────────────────────
//         const allDevices = await Devices.findAll({ where: { isActive: true } });
//         if (!allDevices.length) return res.json({ status: "error", message: "No active devices found" });

//         const cleanBase64 = imgBase64 ? imgBase64.replace(/^data:image\/\w+;base64,/, "") : null;
//         console.log(`[signup] sn=${sn} name=${name} hasface=${!!cleanBase64} hasdoc=${!!docFrontBase64}`);
//         const results = [];

//         for (const device of allDevices) {
//             const deviceResult = { deviceId: device.id, deviceName: device.name, personAdded: false, faceAdded: false, error: null };
//             let host = "146.190.152.146";
//             if (device.id != "1") {
//                 host = "146.190.152.146";
//             }

//             try {
//                 const personRes = await axios.post(`${HEYSTAR_API}/api/v1/person`, {
//                     host, port: 10011, timeout: 15000,
//                     deviceKey: device.deviceKey, secret: device.secret,
//                     type: 1, sn, name: name.trim(), mobile: mobile.trim(),
//                     verifyStyle: 0, expiredType: 0, acTzNumber1: 1,
//                 }, { timeout: 20000 });

//                 if (personRes.data.success) {
//                     deviceResult.personAdded = true;
//                     if (cleanBase64) {
//                         try {
//                             console.log(`[signup] uploading face sn=${sn} device=${device.id} len=${cleanBase64?.length}`);
//                             const faceRes = await axios.post(`${HEYSTAR_API}/api/v1/face`, {
//                                 // host, port: 10011, timeout: 15000,
//                                 // deviceKey: device.deviceKey, secret: device.secret,
//                                 // personSn: sn, imgBase64: cleanBase64, easy: 1,

//                                 host: host,
//                                 port: 10011,
//                                 timeout: 15000,
//                                 deviceKey: device.deviceKey,
//                                 secret: device.secret,
//                                 personSn: sn,
//                                 imgBase64: cleanBase64,
//                                 easy: 0,
//                             }, { timeout: 30000 });
//                             deviceResult.faceAdded = faceRes.data.success;
//                             console.log(`[signup] face result device ${device.id}:`, JSON.stringify(faceRes.data));
//                             if (!faceRes.data.success) console.warn(`Face failed on device ${device.id}:`, faceRes.data.message);
//                         } catch (fe) { console.warn(`Face error on device ${device.id}:`, fe.message); }
//                     } else {
//                         console.log(`[signup] no face — imgBase64 was empty for sn=${sn}`);
//                     }
//                 } else {
//                     deviceResult.error = personRes.data.message;
//                     console.warn(`Person add failed on device ${device.id}:`, personRes.data.message);
//                 }
//             } catch (de) {
//                 deviceResult.error = de.message;
//                 console.warn(`Device ${device.id} unreachable:`, de.message);
//             }

//             results.push(deviceResult);
//             await device.update({ lastSeenAt: new Date() }).catch(() => { });
//         }

//         const anySuccess = results.some((r) => r.personAdded);
//         if (!anySuccess) {
//             const firstError = results.find((r) => r.error)?.error ?? "All devices failed";
//             return res.json({ status: "error", message: `Registration failed: ${firstError}` });
//         }

//         await link.update({
//             signup_name: name.trim(),
//             signup_image: imgBase64 ? "uploaded" : null,
//         });

//         return res.json({ status: "success", message: "Registration complete", sn, devices: results, doc: docResult });

//     } catch (error) {
//         return handleError(res, error);
//     }
// };