<?php

declare(strict_types=1);

require_once __DIR__ . '/HikvisionClient.php';
require_once dirname(__DIR__, 2) . '/helpers/hikvision_helper.php';

class HikvisionNominaService
{
    private HikvisionClient $client;

    public function __construct(HikvisionClient $client)
    {
        $this->client = $client;
    }

    public function obtenerUsuarios(int $offset = 0, int $limit = 30): array
    {
        $payload = [
            'UserInfoSearchCond' => [
                'searchID' => '1',
                'searchResultPosition' => $offset,
                'maxResults' => $limit,
            ]
        ];

        $response = $this->client->post(
            '/ISAPI/AccessControl/UserInfo/Search?format=json',
            $payload,
            ['Content-Type: application/json']
        );

        $parsed = hikvision_parse_response($response['body'] ?? '');
        $data   = is_array($parsed['data']) ? $parsed['data'] : [];

        $users = $this->extractUsers($data);

        $searchBlock = $data['UserInfoSearch'] ?? [];
        $numOfMatches = (int)($searchBlock['numOfMatches'] ?? count($users));
        $totalMatches = (int)($searchBlock['totalMatches'] ?? count($users));

        return [
            'ok'           => !empty($response['ok']),
            'http_code'    => $response['http_code'] ?? null,
            'error'        => $response['error'] ?? null,
            'data'         => $data,
            'users'        => $users,
            'numOfMatches' => $numOfMatches,
            'totalMatches' => $totalMatches,
            'total'        => count($users),
            'response'     => $response,
        ];
    }

    public function obtenerTodosUsuarios(int $pageSize = 30): array
    {
        $offset = 0;
        $allUsers = [];
        $seen = [];
        $totalMatches = null;
        $lastResponse = null;

        while (true) {
            $result = $this->obtenerUsuarios($offset, $pageSize);
            $lastResponse = $result;

            if (empty($result['ok'])) {
                return $result;
            }

            $chunk = $result['users'] ?? [];

            foreach ($chunk as $user) {
                $key = (string)($user['device_user_id'] ?? '');
                if ($key === '') {
                    continue;
                }

                if (!isset($seen[$key])) {
                    $seen[$key] = true;
                    $allUsers[] = $user;
                }
            }

            $numOfMatches = (int)($result['numOfMatches'] ?? count($chunk));
            $totalMatches = (int)($result['totalMatches'] ?? count($allUsers));

            if ($numOfMatches <= 0) {
                break;
            }

            $offset += $numOfMatches;

            if ($offset >= $totalMatches) {
                break;
            }
        }

        return [
            'ok'           => true,
            'http_code'    => $lastResponse['http_code'] ?? 200,
            'error'        => null,
            'data'         => $lastResponse['data'] ?? [],
            'users'        => $allUsers,
            'numOfMatches' => count($allUsers),
            'totalMatches' => $totalMatches ?? count($allUsers),
            'total'        => count($allUsers),
            'response'     => $lastResponse['response'] ?? [],
        ];
    }

    /**
     * Crear / enviar usuario al reloj.
     *
     * Payload esperado:
     * [
     *   'device_user_id' => '1234567',
     *   'name' => 'Juan Perez',
     *   'employee_no' => '1234567'
     * ]
     */
    public function crearUsuario(array $payload): array
    {
        $normalized = $this->normalizeCreatePayload($payload);

        if ($normalized['employeeNo'] === '') {
            return [
                'ok' => false,
                'error' => 'employeeNo / cédula es obligatorio.',
            ];
        }

        if ($normalized['name'] === '') {
            return [
                'ok' => false,
                'error' => 'El nombre es obligatorio.',
            ];
        }

        // Primero intentamos saber si ya existe
        $search = $this->buscarUsuarioPorEmployeeNo($normalized['employeeNo']);
        if (!empty($search['ok']) && !empty($search['found'])) {
            return [
                'ok' => true,
                'already_exists' => true,
                'message' => 'El usuario ya existe en el reloj.',
                'user' => $search['user'] ?? null,
                'search' => $search,
            ];
        }

        $candidateRequests = $this->buildCreateCandidates($normalized);

        $lastAttempt = null;

        foreach ($candidateRequests as $candidate) {
            $method = strtolower((string)$candidate['method']);
            $uri = (string)$candidate['uri'];
            $body = $candidate['body'];
            $headers = $candidate['headers'] ?? ['Content-Type: application/json'];

            try {
                $response = $this->sendRequestByMethod($method, $uri, $body, $headers);
                $parsed = hikvision_parse_response($response['body'] ?? '');
                $data = is_array($parsed['data']) ? $parsed['data'] : [];

                $ok = !empty($response['ok']) && $this->isCreateResponseOk($data, $response);

                $lastAttempt = [
                    'method' => $method,
                    'uri' => $uri,
                    'response' => $response,
                    'data' => $data,
                ];

                if ($ok) {
                    return [
                        'ok' => true,
                        'created' => true,
                        'method' => strtoupper($method),
                        'uri' => $uri,
                        'payload' => $normalized,
                        'response' => $response,
                        'data' => $data,
                    ];
                }
            } catch (Throwable $e) {
                $lastAttempt = [
                    'method' => $method,
                    'uri' => $uri,
                    'exception' => $e->getMessage(),
                ];
            }
        }

        return [
            'ok' => false,
            'error' => 'No se pudo crear el usuario en el reloj con los endpoints probados.',
            'payload' => $normalized,
            'last_attempt' => $lastAttempt,
        ];
    }

    public function crearPersona(array $payload): array
    {
        return $this->crearUsuario($payload);
    }

    public function agregarUsuario(array $payload): array
    {
        return $this->crearUsuario($payload);
    }

    public function registrarUsuario(array $payload): array
    {
        return $this->crearUsuario($payload);
    }

    public function enviarUsuario(array $payload): array
    {
        return $this->crearUsuario($payload);
    }

    public function buscarUsuarioPorEmployeeNo(string $employeeNo): array
    {
        $employeeNo = $this->normalizeCi($employeeNo);
        if ($employeeNo === '') {
            return [
                'ok' => false,
                'found' => false,
                'error' => 'employeeNo vacío.',
            ];
        }

        // En muchos equipos Hikvision la búsqueda general devuelve lotes; buscamos en esos resultados.
        $all = $this->obtenerTodosUsuarios(30);

        if (empty($all['ok'])) {
            return [
                'ok' => false,
                'found' => false,
                'error' => $all['error'] ?? 'No se pudo consultar el reloj.',
                'search' => $all,
            ];
        }

        foreach (($all['users'] ?? []) as $user) {
            $duid = $this->normalizeCi((string)($user['device_user_id'] ?? ''));
            $emp  = $this->normalizeCi((string)($user['empleado_no'] ?? ''));
            if ($duid === $employeeNo || $emp === $employeeNo) {
                return [
                    'ok' => true,
                    'found' => true,
                    'user' => $user,
                    'search' => $all,
                ];
            }
        }

        return [
            'ok' => true,
            'found' => false,
            'search' => $all,
        ];
    }

    private function extractUsers(array $data): array
    {
        $rows = $data['UserInfoSearch']['UserInfo'] ?? [];

        if (!is_array($rows)) {
            return [];
        }

        $result = [];

        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }

            $deviceUserId = $row['employeeNo'] ?? $row['userID'] ?? null;

            if ($deviceUserId === null || $deviceUserId === '') {
                continue;
            }

            $result[] = [
                'device_user_id' => (string)$deviceUserId,
                'nombre'         => $row['name'] ?? null,
                'empleado_no'    => $row['employeeNo'] ?? null,
                'raw_json'       => $row,
            ];
        }

        return $result;
    }

    private function normalizeCreatePayload(array $payload): array
    {
        $deviceUserId = $this->normalizeCi((string)($payload['device_user_id'] ?? $payload['user_id'] ?? $payload['userid'] ?? $payload['ci'] ?? ''));
        $employeeNo   = $this->normalizeCi((string)($payload['employee_no'] ?? $payload['empleado_no'] ?? $deviceUserId));
        $name         = trim((string)($payload['name'] ?? $payload['nombre'] ?? ''));
        $userType     = trim((string)($payload['userType'] ?? 'normal'));

        return [
            'employeeNo' => $employeeNo !== '' ? $employeeNo : $deviceUserId,
            'name'       => $name,
            'userType'   => $userType !== '' ? $userType : 'normal',
            'localUIRight' => false,
            'Valid' => [
                'enable' => true,
                'beginTime' => '2020-01-01T00:00:00',
                'endTime' => '2037-12-31T23:59:59',
                'timeType' => 'local',
            ],
        ];
    }

    private function buildCreateCandidates(array $normalized): array
    {
        $body1 = [
            'UserInfo' => [
                'employeeNo'   => $normalized['employeeNo'],
                'name'         => $normalized['name'],
                'userType'     => $normalized['userType'],
                'localUIRight' => $normalized['localUIRight'],
                'Valid'        => $normalized['Valid'],
            ]
        ];

        // Algunos equipos aceptan este mismo bloque en Record
        $body2 = $body1;

        // Fallback alternativo con userID duplicado
        $body3 = [
            'UserInfo' => [
                'employeeNo'   => $normalized['employeeNo'],
                'userID'       => $normalized['employeeNo'],
                'name'         => $normalized['name'],
                'userType'     => $normalized['userType'],
                'localUIRight' => $normalized['localUIRight'],
                'Valid'        => $normalized['Valid'],
            ]
        ];

        return [
            [
                'method' => 'post',
                'uri' => '/ISAPI/AccessControl/UserInfo/Record?format=json',
                'body' => $body1,
                'headers' => ['Content-Type: application/json'],
            ],
            [
                'method' => 'put',
                'uri' => '/ISAPI/AccessControl/UserInfo/Record?format=json',
                'body' => $body2,
                'headers' => ['Content-Type: application/json'],
            ],
            [
                'method' => 'post',
                'uri' => '/ISAPI/AccessControl/UserInfo/SetUp?format=json',
                'body' => $body3,
                'headers' => ['Content-Type: application/json'],
            ],
        ];
    }

    private function sendRequestByMethod(string $method, string $uri, array $body, array $headers): array
    {
        $method = strtolower($method);

        if ($method === 'post') {
            return $this->client->post($uri, $body, $headers);
        }

        if ($method === 'put') {
            if (method_exists($this->client, 'put')) {
                return $this->client->put($uri, $body, $headers);
            }

            // Fallback si el cliente aún no implementa PUT
            return $this->client->post($uri, $body, $headers);
        }

        throw new RuntimeException('Método HTTP no soportado: ' . $method);
    }

    private function isCreateResponseOk(array $data, array $response): bool
    {
        if (!empty($response['ok'])) {
            if (empty($data)) {
                return true;
            }

            $statusValue = strtoupper((string)(
                $data['statusString'] ??
                $data['status'] ??
                $data['subStatusCode'] ??
                $data['ResponseStatus']['statusString'] ??
                ''
            ));

            if ($statusValue === '' || str_contains($statusValue, 'OK') || str_contains($statusValue, 'SUCCESS')) {
                return true;
            }
        }

        return false;
    }

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