<?php

namespace App\Shared\Logging;

use Doctrine\DBAL\Connection;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Decorates Symfony's minimal `logger` service: every existing LoggerInterface
 * injection keeps writing to stderr (so logs surface in `liara logs`) AND is
 * persisted to the app_log table for `warning` and above, making them queryable
 * from the admin panel. Logging must never slow down or break the request, so the
 * DB write uses a raw DBAL INSERT (independent of the request's ORM transaction)
 * wrapped in a catch-all.
 */
final class DbLogger implements LoggerInterface
{
    private const PERSIST = [
        LogLevel::WARNING,
        LogLevel::ERROR,
        LogLevel::CRITICAL,
        LogLevel::ALERT,
        LogLevel::EMERGENCY,
    ];

    public function __construct(
        private readonly LoggerInterface $inner,
        private readonly Connection $conn,
        private readonly RequestStack $requestStack,
    ) {}

    public function log($level, \Stringable|string $message, array $context = []): void
    {
        $this->inner->log($level, $message, $context);

        if (!in_array((string) $level, self::PERSIST, true)) {
            return;
        }

        try {
            $this->conn->insert('app_log', [
                'level'      => (string) $level,
                'message'    => (string) $message,
                'context'    => $context ? json_encode($this->sanitize($context), JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR) : null,
                'channel'    => 'app',
                'path'       => $this->requestStack->getCurrentRequest()?->getPathInfo(),
                'created_at' => time(),
            ]);
        } catch (\Throwable) {
            // Logging must never break the request; a failed persist stays on stderr only.
        }
    }

    public function emergency(\Stringable|string $message, array $context = []): void { $this->log(LogLevel::EMERGENCY, $message, $context); }
    public function alert(\Stringable|string $message, array $context = []): void     { $this->log(LogLevel::ALERT, $message, $context); }
    public function critical(\Stringable|string $message, array $context = []): void  { $this->log(LogLevel::CRITICAL, $message, $context); }
    public function error(\Stringable|string $message, array $context = []): void     { $this->log(LogLevel::ERROR, $message, $context); }
    public function warning(\Stringable|string $message, array $context = []): void   { $this->log(LogLevel::WARNING, $message, $context); }
    public function notice(\Stringable|string $message, array $context = []): void    { $this->log(LogLevel::NOTICE, $message, $context); }
    public function info(\Stringable|string $message, array $context = []): void      { $this->log(LogLevel::INFO, $message, $context); }
    public function debug(\Stringable|string $message, array $context = []): void     { $this->log(LogLevel::DEBUG, $message, $context); }

    /**
     * Replace a raw Throwable in context with a compact string — the full object
     * is huge and not JSON-friendly. Keeps the rest of the context as-is.
     */
    private function sanitize(array $context): array
    {
        if (isset($context['exception']) && $context['exception'] instanceof \Throwable) {
            $e = $context['exception'];
            $context['exception'] = sprintf('%s: %s @ %s:%d', $e::class, $e->getMessage(), $e->getFile(), $e->getLine());
        }

        return $context;
    }
}
