<?php
declare(strict_types=1);

require_once __DIR__ . '/../hikvision/HikvisionClient.php';
require_once __DIR__ . '/../hikvision/HikvisionDeviceService.php';
require_once __DIR__ . '/../hikvision/HikvisionNominaService.php';
require_once __DIR__ . '/BiometricoRepository.php';

class BiometricoFacade
{
    private BiometricoRepository $repo;

    public function __construct(?BiometricoRepository $repo = null)
    {
        $this->repo = $repo ?? new BiometricoRepository();
    }

    public function obtenerRelojes(): array
    {
        return $this->repo->obtenerRelojes();
    }

    public function obtenerRelojPorId(int $relojId): ?array
    {
        return $this->repo->obtenerRelojPorId($relojId);
    }

    public function detectarReloj(int $relojId): array
    {
        $reloj = $this->repo->obtenerRelojPorId($relojId);

        if (!$reloj) {
            return [
                'ok' => false,
                'error' => 'Reloj no encontrado',
            ];
        }

        $client = $this->crearClienteDesdeReloj($reloj);
        $service = new HikvisionDeviceService($client);

        $resultado = $service->detectar();

        if (!empty($resultado['ok'])) {
            $this->repo->actualizarMetadataReloj(
                $relojId,
                $resultado['resumen']['modelo'] ?? null,
                $resultado['resumen']['serial_number'] ?? null,
                $resultado['resumen']['firmware_version'] ?? null
            );

            $reloj = $this->repo->obtenerRelojPorId($relojId);
        }

        return [
            'ok' => !empty($resultado['ok']),
            'reloj' => $reloj,
            'resultado' => $resultado,
        ];
    }

    public function sincronizarNomina(int $relojId): array
    {
        $reloj = $this->repo->obtenerRelojPorId($relojId);

        if (!$reloj) {
            return [
                'ok' => false,
                'error' => 'Reloj no encontrado',
            ];
        }

        $client = $this->crearClienteDesdeReloj($reloj);
        $service = new HikvisionNominaService($client);

        $resultado = $service->obtenerTodosUsuarios(30);
        $guardado = ['ok' => false, 'procesados' => 0];

        if (!empty($resultado['ok'])) {
            $users = $resultado['users'] ?? [];
            if (is_array($users) && !empty($users)) {
                $guardado = $this->repo->guardarNomina($relojId, $users);
            } else {
                $guardado = ['ok' => true, 'procesados' => 0];
            }
        }

        $nomina = $this->repo->obtenerNominaPorReloj($relojId);

        return [
            'ok' => !empty($resultado['ok']),
            'reloj' => $reloj,
            'resultado' => $resultado,
            'guardado' => $guardado,
            'nomina' => $nomina,
        ];
    }

    public function obtenerDatosMapeo(int $relojId): array
    {
        $reloj = $this->repo->obtenerRelojPorId($relojId);

        if (!$reloj) {
            return [
                'ok' => false,
                'error' => 'Reloj no encontrado',
            ];
        }

        return [
            'ok' => true,
            'reloj' => $reloj,
            'nomina' => $this->repo->obtenerNominaPorReloj($relojId),
            'estudiantes' => $this->repo->obtenerEstudiantesFormateados(),
            'mapeos' => $this->repo->obtenerMapeosPorReloj($relojId),
        ];
    }

    public function guardarMapeos(int $relojId, array $deviceIds, array $estIds, array $activos): array
    {
        $guardado = $this->repo->guardarMapeos($relojId, $deviceIds, $estIds, $activos);
        $datos = $this->obtenerDatosMapeo($relojId);

        return array_merge($datos, [
            'guardado' => $guardado,
        ]);
    }

    /**
     * Enviar persona nueva al reloj.
     * Usa CI como identificador principal del dispositivo.
     */
    public function enviarPersonaAlReloj(
        int $relojId,
        string $ci,
        string $nombreCompleto,
        ?string $empleadoNo = null
    ): array {
        $reloj = $this->repo->obtenerRelojPorId($relojId);

        if (!$reloj) {
            return [
                'ok' => false,
                'error' => 'Reloj no encontrado',
            ];
        }

        $ci = $this->normalizeCi($ci);
        $nombreCompleto = trim($nombreCompleto);
        $empleadoNo = trim((string)$empleadoNo);

        if ($ci === '') {
            return [
                'ok' => false,
                'error' => 'La cédula es obligatoria para enviar al reloj.',
            ];
        }

        if ($nombreCompleto === '') {
            return [
                'ok' => false,
                'error' => 'El nombre es obligatorio para enviar al reloj.',
            ];
        }

        $client = $this->crearClienteDesdeReloj($reloj);
        $service = new HikvisionNominaService($client);

        $payload = [
            'device_user_id' => $ci,
            'ci'             => $ci,
            'user_id'        => $ci,
            'userid'         => $ci,
            'employee_no'    => $empleadoNo !== '' ? $empleadoNo : $ci,
            'empleado_no'    => $empleadoNo !== '' ? $empleadoNo : $ci,
            'name'           => $nombreCompleto,
            'nombre'         => $nombreCompleto,
        ];

        try {
            $resultadoEnvio = $this->invocarAltaNominaService($service, $payload);

            if (empty($resultadoEnvio['ok'])) {
                return [
                    'ok' => false,
                    'error' => $resultadoEnvio['error'] ?? 'No se pudo enviar la persona al reloj.',
                    'reloj' => $reloj,
                    'resultado' => $resultadoEnvio,
                ];
            }

            $guardadoLocal = $this->sincronizarRegistroLocalNomina(
                $relojId,
                $ci,
                $nombreCompleto,
                $empleadoNo !== '' ? $empleadoNo : $ci,
                $payload
            );

            return [
                'ok' => true,
                'reloj' => $reloj,
                'resultado' => $resultadoEnvio,
                'guardado_local' => $guardadoLocal,
                'mensaje' => 'Persona enviada al reloj correctamente.',
            ];
        } catch (Throwable $e) {
            return [
                'ok' => false,
                'error' => $e->getMessage(),
                'reloj' => $reloj,
            ];
        }
    }

    /**
     * Crear estudiante y enviar al reloj.
     * Ojo: este método asume que el alta en estudiantes la maneja el controlador
     * o un paso previo. Acá lo usamos como helper de integración.
     */
    public function enviarEstudianteExistenteAlReloj(int $relojId, array $estudiante): array
    {
        $ci = $this->normalizeCi((string)($estudiante['ci'] ?? ''));
        $nombres = trim((string)($estudiante['nombres'] ?? ''));
        $apellidos = trim((string)($estudiante['apellidos'] ?? ''));

        $nombreCompleto = trim($nombres . ' ' . $apellidos);

        return $this->enviarPersonaAlReloj(
            $relojId,
            $ci,
            $nombreCompleto !== '' ? $nombreCompleto : $ci,
            $ci
        );
    }

    /**
     * Crear espejo local en reloj_nomina para un registro puntual.
     */
    public function sincronizarRegistroLocalNomina(
        int $relojId,
        string $deviceUserId,
        string $nombre,
        ?string $empleadoNo,
        array $raw = []
    ): array {
        $relojId = (int)$relojId;
        $deviceUserId = $this->normalizeCi($deviceUserId);
        $nombre = trim($nombre);
        $empleadoNo = trim((string)$empleadoNo);

        if ($relojId <= 0 || $deviceUserId === '') {
            return [
                'ok' => false,
                'error' => 'Datos inválidos para sincronizar registro local.',
            ];
        }

        try {
            $pdo = db();

            $rawJson = !empty($raw) ? json_encode($raw, JSON_UNESCAPED_UNICODE) : null;

            $st = $pdo->prepare("
                SELECT id
                FROM reloj_nomina
                WHERE reloj_id = ?
                  AND device_user_id = ?
                LIMIT 1
            ");
            $st->execute([$relojId, $deviceUserId]);
            $existingId = (int)($st->fetchColumn() ?: 0);

            if ($existingId > 0) {
                $pdo->prepare("
                    UPDATE reloj_nomina
                    SET nombre = ?,
                        empleado_no = ?,
                        raw_json = ?,
                        actualizado_en = NOW()
                    WHERE id = ?
                ")->execute([
                    $nombre !== '' ? $nombre : null,
                    $empleadoNo !== '' ? $empleadoNo : null,
                    $rawJson,
                    $existingId,
                ]);

                return [
                    'ok' => true,
                    'accion' => 'update',
                    'id' => $existingId,
                ];
            }

            $pdo->prepare("
                INSERT INTO reloj_nomina
                    (reloj_id, device_user_id, nombre, empleado_no, raw_json, actualizado_en)
                VALUES
                    (?, ?, ?, ?, ?, NOW())
            ")->execute([
                $relojId,
                $deviceUserId,
                $nombre !== '' ? $nombre : null,
                $empleadoNo !== '' ? $empleadoNo : null,
                $rawJson,
            ]);

            return [
                'ok' => true,
                'accion' => 'insert',
                'id' => (int)$pdo->lastInsertId(),
            ];
        } catch (Throwable $e) {
            return [
                'ok' => false,
                'error' => $e->getMessage(),
            ];
        }
    }

    private function crearClienteDesdeReloj(array $reloj): HikvisionClient
    {
        return new HikvisionClient([
            'ip' => $reloj['ip'],
            'port' => $reloj['puerto'],
            'user' => $reloj['usuario'],
            'pass' => $reloj['password'],
            'protocol' => $reloj['protocolo'] ?? 'http',
        ]);
    }

    private function normalizeCi(string $ci): string
    {
        $ci = trim($ci);
        $ci = preg_replace('/\s+/', '', $ci);
        return (string)$ci;
    }

    /**
     * Intenta varios nombres de método posibles del service de nómina.
     * Esto permite que avancemos aunque todavía no hayamos visto tu implementation exacta.
     */
    private function invocarAltaNominaService(HikvisionNominaService $service, array $payload): array
    {
        $candidatos = [
            'crearUsuario',
            'crearPersona',
            'agregarUsuario',
            'registrarUsuario',
            'enviarUsuario',
            'crearEmpleado',
            'registrarPersona',
        ];

        foreach ($candidatos as $metodo) {
            if (method_exists($service, $metodo)) {
                $resultado = $service->{$metodo}($payload);
                if (is_array($resultado)) {
                    return $resultado;
                }
                return [
                    'ok' => true,
                    'raw_result' => $resultado,
                    'metodo' => $metodo,
                ];
            }
        }

        return [
            'ok' => false,
            'error' => 'HikvisionNominaService aún no tiene implementado un método de alta de usuario compatible.',
        ];
    }
}