<?php
declare(strict_types=1);

require_once __DIR__ . '/../../app/db.php';

header('Content-Type: application/json; charset=utf-8');

$pdo = db();

$sourceIp      = $_SERVER['REMOTE_ADDR'] ?? null;
$contentType   = $_SERVER['CONTENT_TYPE'] ?? ($_SERVER['HTTP_CONTENT_TYPE'] ?? '');
$relojIdManual = isset($_GET['reloj_id']) ? (int)($_GET['reloj_id']) : 0;
$rawPayload    = file_get_contents('php://input') ?: '';

function respond_ok(array $extra = []): void
{
    http_response_code(200);
    echo json_encode(array_merge([
        'ok' => true,
        'message' => 'Evento recibido'
    ], $extra), JSON_UNESCAPED_UNICODE);
    exit;
}

function respond_error(string $message, int $code = 400, array $extra = []): void
{
    http_response_code($code);
    echo json_encode(array_merge([
        'ok' => false,
        'error' => $message
    ], $extra), JSON_UNESCAPED_UNICODE);
    exit;
}

function is_json_string(string $value): bool
{
    if (trim($value) === '') {
        return false;
    }

    json_decode($value, true);
    return json_last_error() === JSON_ERROR_NONE;
}

function parse_xml_to_array(string $xmlString): array
{
    libxml_use_internal_errors(true);
    $xml = simplexml_load_string($xmlString, 'SimpleXMLElement', LIBXML_NOCDATA);

    if ($xml === false) {
        return [];
    }

    $json = json_encode($xml, JSON_UNESCAPED_UNICODE);
    return is_string($json) ? (json_decode($json, true) ?: []) : [];
}

function array_get_path(array $data, array $paths, $default = null)
{
    foreach ($paths as $path) {
        $segments = explode('.', $path);
        $value = $data;
        $found = true;

        foreach ($segments as $segment) {
            if (is_array($value) && array_key_exists($segment, $value)) {
                $value = $value[$segment];
            } else {
                $found = false;
                break;
            }
        }

        if ($found) {
            return $value;
        }
    }

    return $default;
}

function normalize_datetime(?string $value): ?string
{
    if (!$value) {
        return null;
    }

    $value = trim($value);
    if ($value === '') {
        return null;
    }

    try {
        $dt = new DateTime($value);
        return $dt->format('Y-m-d H:i:s');
    } catch (Throwable $e) {
        return null;
    }
}

function collect_uploaded_files(): array
{
    $result = [];

    foreach ($_FILES as $field => $info) {
        if (!is_array($info)) {
            continue;
        }

        if (isset($info['tmp_name']) && is_string($info['tmp_name'])) {
            $content = '';
            if ($info['tmp_name'] !== '' && is_uploaded_file($info['tmp_name'])) {
                $content = file_get_contents($info['tmp_name']) ?: '';
            }

            $result[$field] = [
                'name' => $info['name'] ?? '',
                'type' => $info['type'] ?? '',
                'size' => $info['size'] ?? 0,
                'error' => $info['error'] ?? 0,
                'content' => $content,
            ];
        }
    }

    return $result;
}

function build_log_payload(string $rawPayload, array $postData, array $fileData): string
{
    if (trim($rawPayload) !== '') {
        return $rawPayload;
    }

    if (!empty($postData) || !empty($fileData)) {
        return json_encode([
            '_post' => $postData,
            '_files' => $fileData,
        ], JSON_UNESCAPED_UNICODE);
    }

    return '';
}

function extract_candidate_payloads(string $rawPayload, array $postData, array $fileData): array
{
    $candidates = [];

    if (trim($rawPayload) !== '') {
        $candidates[] = $rawPayload;
    }

    foreach ($postData as $key => $value) {
        if (is_scalar($value)) {
            $text = trim((string)$value);
            if ($text !== '') {
                $candidates[] = $text;
            }
        }
    }

    foreach ($fileData as $file) {
        $content = trim((string)($file['content'] ?? ''));
        if ($content !== '') {
            $candidates[] = $content;
        }
    }

    return $candidates;
}

function unwrap_embedded_payloads(array $data): array
{
    $keys = [
        'event_log',
        'eventLog',
        'json',
        'payload',
        'EventNotificationAlert',
    ];

    foreach ($keys as $key) {
        if (!array_key_exists($key, $data)) {
            continue;
        }

        $value = $data[$key];

        if (is_string($value) && is_json_string($value)) {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                return $decoded;
            }
        }

        if (is_string($value)) {
            $xmlParsed = parse_xml_to_array($value);
            if (!empty($xmlParsed)) {
                return $xmlParsed;
            }
        }

        if (is_array($value)) {
            return $value;
        }
    }

    return $data;
}

function find_first_key_recursive($data, array $keys)
{
    if (!is_array($data)) {
        return null;
    }

    foreach ($keys as $key) {
        if (array_key_exists($key, $data) && is_scalar($data[$key])) {
            $value = trim((string)$data[$key]);
            if ($value !== '') {
                return $value;
            }
        }
    }

    foreach ($data as $value) {
        if (is_array($value)) {
            $found = find_first_key_recursive($value, $keys);
            if ($found !== null && trim((string)$found) !== '') {
                return $found;
            }
        }
    }

    return null;
}

$postData = $_POST ?? [];
$fileData = collect_uploaded_files();
$logPayload = build_log_payload($rawPayload, $postData, $fileData);

/*
|--------------------------------------------------------------------------
| Guardar log bruto
|--------------------------------------------------------------------------
*/
try {
    $stmtLog = $pdo->prepare("
        INSERT INTO hikvision_event_log (source_ip, content_type, payload, creado_en)
        VALUES (?, ?, ?, NOW())
    ");
    $stmtLog->execute([$sourceIp, $contentType, $logPayload]);
} catch (Throwable $e) {
    // No frenar recepción si falla el log
}

/*
|--------------------------------------------------------------------------
| Detectar reloj
|--------------------------------------------------------------------------
*/
$reloj = null;

if ($relojIdManual > 0) {
    $stmtReloj = $pdo->prepare("
        SELECT *
        FROM relojes_biometricos
        WHERE id = ?
        LIMIT 1
    ");
    $stmtReloj->execute([$relojIdManual]);
    $reloj = $stmtReloj->fetch(PDO::FETCH_ASSOC);
} else {
    $stmtReloj = $pdo->prepare("
        SELECT *
        FROM relojes_biometricos
        WHERE ip = ?
        LIMIT 1
    ");
    $stmtReloj->execute([$sourceIp]);
    $reloj = $stmtReloj->fetch(PDO::FETCH_ASSOC);
}

if (!$reloj) {
    respond_ok([
        'warning' => 'No se encontró reloj por IP o reloj_id',
        'source_ip' => $sourceIp,
        'reloj_id_manual' => $relojIdManual,
    ]);
}

$relojId = (int)$reloj['id'];

/*
|--------------------------------------------------------------------------
| Buscar payload útil
|--------------------------------------------------------------------------
*/
$candidates = extract_candidate_payloads($rawPayload, $postData, $fileData);

$parsed = [];
$payloadText = '';
$format = 'unknown';

foreach ($candidates as $candidate) {
    if (is_json_string($candidate)) {
        $parsed = json_decode($candidate, true) ?: [];
        $payloadText = $candidate;
        $format = 'json';
        break;
    }

    $xmlParsed = parse_xml_to_array($candidate);
    if (!empty($xmlParsed)) {
        $parsed = $xmlParsed;
        $payloadText = $candidate;
        $format = 'xml';
        break;
    }
}

/*
|--------------------------------------------------------------------------
| Si no encontramos JSON/XML, usar directamente $_POST como fuente
|--------------------------------------------------------------------------
*/
if (empty($parsed) && !empty($postData)) {
    $parsed = $postData;
    $payloadText = json_encode($postData, JSON_UNESCAPED_UNICODE);
    $format = 'post';
}

/*
|--------------------------------------------------------------------------
| Desenvolver payload embebido: ej. $_POST['event_log'] con JSON adentro
|--------------------------------------------------------------------------
*/
$parsed = unwrap_embedded_payloads($parsed);

/*
|--------------------------------------------------------------------------
| Extraer datos del evento
|--------------------------------------------------------------------------
*/
$deviceUserId = array_get_path($parsed, [
    'employeeNoString',
    'employeeNo',
    'userID',
    'deviceUserId',
    'employeeId',
    'cardNo',
    'majorInfo.employeeNoString',
    'minorInfo.employeeNoString',
    'Event.employeeNoString',
    'AcsEventInfo.employeeNoString',
    'AcsEvent.employeeNoString',
    'AccessControllerEvent.employeeNoString',
    'AccessControllerEvent.employeeNo',
], null);

if ($deviceUserId === null || trim((string)$deviceUserId) === '') {
    $deviceUserId = find_first_key_recursive($parsed, [
        'employeeNoString',
        'employeeNo',
        'userID',
        'deviceUserId',
        'employeeId',
        'cardNo',
    ]);
}

$fechaHoraRaw = array_get_path($parsed, [
    'dateTime',
    'time',
    'Time',
    'eventTime',
    'Event.time',
    'AcsEventInfo.time',
    'AcsEvent.time',
    'AccessControllerEvent.time',
    'AccessControllerEvent.dateTime',
], null);

if ($fechaHoraRaw === null || trim((string)$fechaHoraRaw) === '') {
    $fechaHoraRaw = find_first_key_recursive($parsed, [
        'time',
        'dateTime',
        'eventTime',
    ]);
}

$tipoEvento = array_get_path($parsed, [
    'type',
    'eventType',
    'minor',
    'eventDescription',
    'Event.type',
    'AcsEventInfo.minor',
    'AcsEvent.minor',
    'AccessControllerEvent.minor',
], null);

if ($tipoEvento === null || trim((string)$tipoEvento) === '') {
    $tipoEvento = find_first_key_recursive($parsed, [
        'type',
        'eventType',
        'minor',
        'eventDescription',
    ]);
}

/*
|--------------------------------------------------------------------------
| Fallback por regex sobre texto útil o log payload
|--------------------------------------------------------------------------
*/
$textForRegex = $payloadText !== '' ? $payloadText : $logPayload;

if (($deviceUserId === null || trim((string)$deviceUserId) === '') &&
    preg_match('/"employeeNoString"\s*:\s*"([^"]+)"/i', $textForRegex, $m)) {
    $deviceUserId = trim($m[1]);
}

if (($deviceUserId === null || trim((string)$deviceUserId) === '') &&
    preg_match('/"employeeNo"\s*:\s*"([^"]+)"/i', $textForRegex, $m)) {
    $deviceUserId = trim($m[1]);
}

if (($deviceUserId === null || trim((string)$deviceUserId) === '') &&
    preg_match('/"employeeNo"\s*:\s*([0-9]+)/i', $textForRegex, $m)) {
    $deviceUserId = trim($m[1]);
}

if (($deviceUserId === null || trim((string)$deviceUserId) === '') &&
    preg_match('/"cardNo"\s*:\s*"([^"]+)"/i', $textForRegex, $m)) {
    $deviceUserId = trim($m[1]);
}

if (($deviceUserId === null || trim((string)$deviceUserId) === '') &&
    preg_match('/<employeeNoString>(.*?)<\/employeeNoString>/i', $textForRegex, $m)) {
    $deviceUserId = trim($m[1]);
}

if (($fechaHoraRaw === null || trim((string)$fechaHoraRaw) === '') &&
    preg_match('/"time"\s*:\s*"([^"]+)"/i', $textForRegex, $m)) {
    $fechaHoraRaw = trim($m[1]);
}

if (($fechaHoraRaw === null || trim((string)$fechaHoraRaw) === '') &&
    preg_match('/"dateTime"\s*:\s*"([^"]+)"/i', $textForRegex, $m)) {
    $fechaHoraRaw = trim($m[1]);
}

if (($fechaHoraRaw === null || trim((string)$fechaHoraRaw) === '') &&
    preg_match('/<(?:time|dateTime)>(.*?)<\/(?:time|dateTime)>/i', $textForRegex, $m)) {
    $fechaHoraRaw = trim($m[1]);
}

if (($tipoEvento === null || trim((string)$tipoEvento) === '') &&
    preg_match('/"minor"\s*:\s*"([^"]+)"/i', $textForRegex, $m)) {
    $tipoEvento = trim($m[1]);
}

if (($tipoEvento === null || trim((string)$tipoEvento) === '') &&
    preg_match('/"eventType"\s*:\s*"([^"]+)"/i', $textForRegex, $m)) {
    $tipoEvento = trim($m[1]);
}

if (($tipoEvento === null || trim((string)$tipoEvento) === '') &&
    preg_match('/<minor>(.*?)<\/minor>/i', $textForRegex, $m)) {
    $tipoEvento = trim($m[1]);
}

$deviceUserId = is_scalar($deviceUserId) ? trim((string)$deviceUserId) : '';
$fechaHora = normalize_datetime(is_scalar($fechaHoraRaw) ? (string)$fechaHoraRaw : null);
$tipoEvento = is_scalar($tipoEvento) ? trim((string)$tipoEvento) : 'EVENT';
$fechaHora = $fechaHora ?: date('Y-m-d H:i:s');

if ($deviceUserId === '') {
    respond_ok([
        'warning' => 'Payload recibido pero no se pudo extraer device_user_id',
        'source_ip' => $sourceIp,
        'format' => $format,
        'post_keys' => array_keys($postData),
        'file_keys' => array_keys($fileData),
        'parsed_keys' => array_keys($parsed),
    ]);
}

/*
|--------------------------------------------------------------------------
| Insertar marcación
|--------------------------------------------------------------------------
*/
try {
    $stmtIns = $pdo->prepare("
        INSERT INTO marcaciones
        (reloj_id, device_user_id, fecha_hora, tipo_evento, raw_json, creado_en)
        VALUES (?, ?, ?, ?, ?, NOW())
        ON DUPLICATE KEY UPDATE
            tipo_evento = VALUES(tipo_evento),
            raw_json = VALUES(raw_json)
    ");

    $stmtIns->execute([
        $relojId,
        $deviceUserId,
        $fechaHora,
        $tipoEvento,
        $payloadText !== '' ? $payloadText : $logPayload
    ]);

    respond_ok([
        'reloj_id' => $relojId,
        'device_user_id' => $deviceUserId,
        'fecha_hora' => $fechaHora,
        'tipo_evento' => $tipoEvento,
        'format' => $format,
        'source_ip' => $sourceIp,
    ]);
} catch (Throwable $e) {
    respond_error('Error guardando marcación', 500, [
        'detail' => $e->getMessage(),
        'reloj_id' => $relojId,
        'device_user_id' => $deviceUserId,
    ]);
}