<?php

declare(strict_types=1);

namespace ApiModel\Infrastructure\Logging;

use ApiModel\Shared\RequestContext;
use PDO;
use Throwable;

/**
 * Una línea JSON por evento (adecuado para agregación en Loki, CloudWatch, etc.).
 */
final class StructuredLog
{
    private static ?PDO $pdo = null;
    private const DB_TABLE = 'auth.logs';

    public static function setPdo(?PDO $pdo): void
    {
        self::$pdo = $pdo;
    }

    /** @param array<string, mixed> $payload */
    private static function writeDb(array $payload): void
    {
        if (self::$pdo === null) {
            return;
        }

        $stmt = self::$pdo->prepare(
            'INSERT INTO ' . self::DB_TABLE . ' (ts, level, type, request_id, user_id, user_role, method, path, status, duration_ms, payload)
             VALUES (?, ?, ?, ?, ?::uuid, ?, ?, ?, ?, ?, ?::jsonb)',
        );

        $stmt->execute([
            $payload['ts'] ?? gmdate('c'),
            (string) ($payload['level'] ?? 'info'),
            (string) ($payload['type'] ?? 'event'),
            isset($payload['request_id']) ? (string) $payload['request_id'] : null,
            isset($payload['user_id']) && $payload['user_id'] !== '' ? (string) $payload['user_id'] : null,
            isset($payload['user_role']) ? (string) $payload['user_role'] : null,
            isset($payload['method']) ? (string) $payload['method'] : null,
            isset($payload['path']) ? (string) $payload['path'] : null,
            isset($payload['status']) ? (int) $payload['status'] : null,
            isset($payload['duration_ms']) ? (float) $payload['duration_ms'] : null,
            json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
        ]);
    }

    /** @param array<string, mixed> $payload */
    private static function writeStderr(array $payload): void
    {
        error_log(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));
    }

    /** @param array<string, mixed> $context */
    public static function emit(string $level, string $type, array $context = []): void
    {
        $payload = array_merge([
            'ts' => gmdate('c'),
            'level' => $level,
            'type' => $type,
            'request_id' => RequestContext::requestId(),
        ], $context);

        try {
            self::writeDb($payload);
            self::writeStderr($payload);
        } catch (Throwable) {
            error_log('[api] structured_log_failed type=' . $type);
        }
    }

    public static function exception(Throwable $e, string $type = 'exception', string $level = 'error'): void
    {
        self::emit($level, $type, [
            'message' => $e->getMessage(),
            'class' => $e::class,
            'code' => $e->getCode(),
        ]);
    }
}
