<?php

declare(strict_types=1);

namespace StaticPHP\Command;

use StaticPHP\DI\ApplicationContext;
use StaticPHP\Doctor\Doctor;
use StaticPHP\Exception\ExceptionHandler;
use StaticPHP\Exception\SPCException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Logger\ConsoleColor;

abstract class BaseCommand extends Command
{
    use ReturnCode;

    /**
     * The message of the day (MOTD) displayed when the command is run.
     * You can customize this to show your application's name and version if you are using SPC in vendor mode.
     */
    public static string $motd = ' ____  _        _   _      ____  _   _ ____
/ ___|| |_ __ _| |_(_) ___|  _ \| | | |  _ \
\___ \| __/ _` | __| |/ __| |_) | |_| | |_) |
 ___) | || (_| | |_| | (__|  __/|  _  |  __/
|____/ \__\__,_|\__|_|\___|_|   |_| |_|_|    {version}
';

    protected bool $no_motd = false;

    protected InputInterface $input;

    protected OutputInterface $output;

    public function __construct(?string $name = null)
    {
        parent::__construct($name);
        $this->addOption('debug', null, null, '(deprecated) Enable debug mode');
        $this->addOption('no-motd', null, null, 'Disable motd');
    }

    public function initialize(InputInterface $input, OutputInterface $output): void
    {
        $this->input = $input;
        $this->output = $output;

        // Bind command context to ApplicationContext
        ApplicationContext::bindCommandContext($input, $output);

        if ($input->getOption('no-motd')) {
            $this->no_motd = true;
        }

        set_error_handler(static function ($error_no, $error_msg, $error_file, $error_line) {
            $tips = [
                E_WARNING => ['PHP Warning: ', 'warning'],
                E_NOTICE => ['PHP Notice: ', 'notice'],
                E_USER_ERROR => ['PHP Error: ', 'error'],
                E_USER_WARNING => ['PHP Warning: ', 'warning'],
                E_USER_NOTICE => ['PHP Notice: ', 'notice'],
                E_RECOVERABLE_ERROR => ['PHP Recoverable Error: ', 'error'],
                E_DEPRECATED => ['PHP Deprecated: ', 'notice'],
                E_USER_DEPRECATED => ['PHP User Deprecated: ', 'notice'],
            ];
            $level_tip = $tips[$error_no] ?? ['PHP Unknown: ', 'error'];
            $error = $level_tip[0] . $error_msg . ' in ' . $error_file . ' on ' . $error_line;
            logger()->{$level_tip[1]}($error);
            // 如果 return false 则错误会继续递交给 PHP 标准错误处理
            return true;
        });
        $version = $this->getVersionWithCommit();
        if (!$this->no_motd) {
            $str = str_replace('{version}', '' . ConsoleColor::none("v{$version}"), '' . ConsoleColor::magenta(self::$motd));
            $this->output->writeln($this->input->getOption('no-ansi') ? strip_ansi_colors($str) : $str);
        }
    }

    abstract public function handle(): int;

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        try {
            // handle verbose option
            $level = match ($this->output->getVerbosity()) {
                OutputInterface::VERBOSITY_VERBOSE => 'info',
                OutputInterface::VERBOSITY_VERY_VERBOSE, OutputInterface::VERBOSITY_DEBUG => 'debug',
                default => 'warning',
            };
            $isDebug = false;
            // if '--debug' is set, override log level to debug
            if ($this->input->getOption('debug')) {
                $level = 'debug';
                logger()->warning('The --debug option is deprecated and will be removed in future versions. Please use -vv or -vvv to enable debug mode.');
                $this->output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
                $isDebug = true;
            }
            logger()->setLevel($level);

            // ansi
            if ($this->input->getOption('no-ansi')) {
                logger()->setDecorated(false);
            }

            // Set debug mode in ApplicationContext
            $isDebug = $isDebug ?: $this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG;
            ApplicationContext::setDebug($isDebug);

            // show raw argv list for logger()->debug
            logger()->debug('argv: ' . implode(' ', $_SERVER['argv']));
            return $this->handle();
        } /* @noinspection PhpRedundantCatchClauseInspection */ catch (SPCException $e) {
            // Handle SPCException and log it
            return ExceptionHandler::handleSPCException($e);
        } catch (\Throwable $e) {
            // Handle any other exceptions
            return ExceptionHandler::handleDefaultException($e);
        }
    }

    /**
     * Warn the user if doctor has not been run (or is outdated).
     * Set SPC_SKIP_DOCTOR_CHECK=1 to suppress.
     */
    protected function checkDoctorCache(): void
    {
        if (getenv('SPC_SKIP_DOCTOR_CHECK') || Doctor::isHealthy()) {
            return;
        }
        $this->output->writeln('');
        $this->output->writeln('<comment>[WARNING] Please run `spc doctor` first to verify your build environment.</comment>');
        $this->output->writeln('');
        sleep(2);
    }

    protected function getOption(string $name): mixed
    {
        return $this->input->getOption($name);
    }

    protected function getArgument(string $name): mixed
    {
        return $this->input->getArgument($name);
    }

    /**
     * Get version string with git commit short ID if available.
     */
    private function getVersionWithCommit(): string
    {
        $version = $this->getApplication()->getVersion();

        // Don't show commit ID when running in phar
        if (\Phar::running()) {
            $stable = file_exists(ROOT_DIR . '/src/.release') ? 'stable' : 'unstable';
            return "{$version} ({$stable})";
        }

        $commitId = $this->getGitCommitShortId();
        if ($commitId) {
            return "{$version} ({$commitId})";
        }

        return $version;
    }

    /**
     * Get git commit short ID without executing git command.
     */
    private function getGitCommitShortId(): ?string
    {
        try {
            $gitDir = ROOT_DIR . '/.git';

            if (!is_dir($gitDir)) {
                return null;
            }

            $headFile = $gitDir . '/HEAD';
            if (!file_exists($headFile)) {
                return null;
            }

            $head = trim(file_get_contents($headFile));

            // If HEAD contains 'ref:', it's a branch reference
            if (str_starts_with($head, 'ref: ')) {
                $ref = substr($head, 5);
                $refFile = $gitDir . '/' . $ref;

                if (file_exists($refFile)) {
                    $commit = trim(file_get_contents($refFile));
                    return substr($commit, 0, 7);
                }
            } else {
                // HEAD contains the commit hash directly (detached HEAD)
                return substr($head, 0, 7);
            }
        } catch (\Throwable) {
            // Silently fail if we can't read git info
        }

        return null;
    }
}
