<?php

namespace Velis\Log;

use DateTimeZone;
use Exception;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Logger as MonologLogger;
use Sentry\Monolog\Handler as SentryHandler;
use Sentry\SentrySdk;
use Velis\App;
use Velis\Log\Processor\CorrelationIdProcessor;
use Velis\Log\Sentry\BreadcrumbHelper;

readonly class LoggerFactory
{
    public function __construct(private DateTimeZone $timezone)
    {
    }

    /**
     * @throws Exception
     */
    public function createLogger(LoggerFactoryParam $param): LoggerInterface
    {
        $monologLogger = $this->createMonologLogger($param);

        return new Logger($monologLogger);
    }

    /**
     * @throws Exception
     */
    private function createMonologLogger(LoggerFactoryParam $param): MonologLogger
    {
        $monologLogger = new MonologLogger($param->channelName, [], [], $this->timezone);

        if (is_string($param->stream)) {
            $streamHandler = $this->createStreamHandler($param->stream);
            $monologLogger->pushHandler($streamHandler);
        }

        $monologLogger
            ->pushHandler($this->createSentryHandler())
                ->pushProcessor(function ($record) {
                    App::getService(BreadcrumbHelper::class)
                        ->addLog($record);

                    return $record;
                })
        ;

        return $monologLogger
            ->pushProcessor(new CorrelationIdProcessor())
        ;
    }

    private function createStreamHandler(string $stream): StreamHandler
    {
        // phpcs:disable PHPCS_SecurityAudit.BadFunctions.FilesystemFunctions.WarnFilesystem
        $directory = dirname($stream);
        if (!is_dir($directory)) {
            mkdir($directory, 0775, true);
        }
        // phpcs:enable

        $streamHandler = new StreamHandler($stream);
        $lineFormatter = new LineFormatter(
            format: "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n",
            dateFormat: "Y-m-d\TH:i:sO",
            allowInlineLineBreaks: true,
        );
        $streamHandler->setFormatter($lineFormatter);

        return $streamHandler;
    }

    /**
     * @throws Exception
     */
    private function createSentryHandler(): SentryHandler
    {
        $hub = SentrySdk::getCurrentHub();
        if (!$hub->getClient()) {
            throw new Exception('Sentry client not initialized');
        }

        return new SentryHandler(
            hub: $hub,
            level: MonologLogger::ERROR,
            fillExtraContext: true,
        );
    }
}
