<?php

declare(strict_types=1);

namespace Apexl\Io\Logger\Commands;

use Apexl\Io\Logger\Dtos\LoggerConfig;
use Apexl\Io\Logger\Enums\LogLevel;
use Apexl\Io\Logger\Services\LoggerStore;
use Apexl\Io\System\Commands\DaedalusCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * Writes a message straight to a log stream.
 *
 * The point is to exercise the logging configuration itself rather than to record
 * anything: it answers "is this stream reaching the handler I think it is, at the level
 * I think it is" without needing an application code path that happens to log. Naming a
 * stream that is not configured is a legitimate use — with `enableDynamicStreams` on,
 * {@see LoggerStore} will create it, which is the quickest way to confirm that.
 */
#[AsCommand(
    name: 'debug:log',
    description: 'Write a message to a log stream'
)]
class DebugLogCommand extends DaedalusCommand
{
    public function __construct(private readonly LoggerConfig $loggerConfig)
    {
        parent::__construct();
    }

    protected function configure(): void
    {
        parent::configure();

        $this->addArgument(
            'message',
            InputArgument::REQUIRED,
            'The message to log',
        );

        $this->addOption(
            'stream',
            's',
            InputOption::VALUE_REQUIRED,
            'The log stream to write to',
            LoggerStore::DEFAULT__LOGGER_STREAM,
        );

        $this->addOption(
            'level',
            'l',
            InputOption::VALUE_REQUIRED,
            sprintf('The level to log at (%s)', $this->levels()),
            LogLevel::DEBUG->value,
        );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        $level = LogLevel::tryFrom(strtolower((string) $input->getOption('level')));

        if ($level === null) {
            $io->error(sprintf(
                'Unknown log level "%s". Expected one of: %s',
                (string) $input->getOption('level'),
                $this->levels(),
            ));

            return self::FAILURE;
        }

        $stream = (string) $input->getOption('stream');
        $message = (string) $input->getArgument('message');

        \Apexl\Io\Logger\logger($stream)->log($level->value, $message);

        $io->success(sprintf('Logged to "%s" at %s.', $stream, $level->value));

        $configured = $this->configuredLevel($stream);

        // Reporting success on a message the stream then discards is the one way this
        // command could actively mislead, so say so. The stream level is only the first
        // gate — an individual handler may be configured higher still.
        if ($this->isBelow($level, $configured)) {
            $io->warning(sprintf(
                'Stream "%s" is configured at %s, so a %s message will not be written. '
                . 'Individual handlers may filter higher again.',
                $stream,
                $configured->value,
                $level->value,
            ));
        }

        return self::SUCCESS;
    }

    /** An unconfigured stream is created dynamically at the global level. */
    private function configuredLevel(string $stream): LogLevel
    {
        return $this->loggerConfig->streams[$stream]->level ?? $this->loggerConfig->level;
    }

    /** {@see LogLevel} is declared most severe first, as in PSR-3. */
    private function isBelow(LogLevel $level, LogLevel $threshold): bool
    {
        $cases = LogLevel::cases();

        return array_search($level, $cases, true) > array_search($threshold, $cases, true);
    }

    private function levels(): string
    {
        return implode(', ', array_column(LogLevel::cases(), 'value'));
    }
}
