<?php
declare(strict_types=1);

namespace App\Framework\Console;

use App\Framework\Console\Components\InteractiveMenu;
use App\Framework\Console\Exceptions\CommandNotFoundException;
use App\Framework\DI\Container;
use App\Framework\Discovery\Results\DiscoveryResults;
use ReflectionClass;
use ReflectionMethod;
use Throwable;

final class ConsoleApplication
{
    private array $commands = [];
    private ConsoleOutputInterface $output;

    public function __construct(
        private readonly Container $container,
        private readonly string    $scriptName = 'console',
        private readonly string    $title = 'Console Application',
        ?ConsoleOutputInterface    $output = null,

    ) {
        $this->output = $output ?? new ConsoleOutput();

        // Setze den Fenstertitel
        $this->output->writeWindowTitle($this->title);

        $results = $this->container->get(DiscoveryResults::class);

        foreach($results->get(ConsoleCommand::class) as $command) {

            $this->commands[$command['attribute_data']['name']] = [
                'instance' => $this->container->get($command['class']),
                'method' => $command['method'],
                'description' => $command['attribute_data']['description'] ?? ['Keine Beschreibung verfügbar'],
                'reflection' => new ReflectionMethod($command['class'], $command['method'])
            ];
        }
    }

    /**
     * Registriert alle Kommandos aus einer Klasse
     */

    public function registerCommands(object $commandClass): void
    {
        $reflection = new ReflectionClass($commandClass);

        foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
            $attributes = $method->getAttributes(ConsoleCommand::class);

            foreach ($attributes as $attribute) {
                /** @var ConsoleCommand $command */
                $command = $attribute->newInstance();

                $this->commands[$command->name] = [
                    'instance' => $commandClass,
                    'method' => $method->getName(),
                    'description' => $command->description,
                    'reflection' => $method
                ];
            }
        }
    }


    /**
     * Führt ein Kommando aus
     */
    public function run(array $argv): int
    {
        if (count($argv) < 2) {
            $this->showHelp();
            return ExitCode::SUCCESS->value;
        }

        $commandName = $argv[1];
        $arguments = array_slice($argv, 2);

        if (in_array($commandName, ['help', '--help', '-h'])) {
            $this->showHelp();
            return ExitCode::SUCCESS->value;
        }

        if (!isset($this->commands[$commandName])) {
            $this->output->writeError("Kommando '{$commandName}' nicht gefunden.");
            $this->showHelp();
            return ExitCode::COMMAND_NOT_FOUND->value;
        }

        return $this->executeCommand($commandName, $arguments)->value;
    }

    private function executeCommand(string $commandName, array $arguments): ExitCode
    {
        $command = $this->commands[$commandName];
        $instance = $command['instance'];
        $method = $command['method'];

        try {
            // Setze den Fenstertitel für das aktuelle Kommando
            $this->output->writeWindowTitle("{$this->scriptName} - {$commandName}");

            // Erstelle ConsoleInput
            $input = new ConsoleInput($arguments, $this->output);

            // Führe das Kommando aus
            $result = $instance->$method($input, $this->output);

            // Behandle verschiedene Rückgabetypen
            if ($result instanceof ExitCode) {
                return $result;
            }

            if (is_int($result)) {
                return ExitCode::from($result);
            }

            // Standardmäßig Erfolg, wenn nichts anderes zurückgegeben wird
            return ExitCode::SUCCESS;

        } catch (CommandNotFoundException $e) {
            $this->output->writeError("Kommando nicht gefunden: " . $e->getMessage());
            return ExitCode::COMMAND_NOT_FOUND;
        } catch (Throwable $e) {
            $this->output->writeError("Fehler beim Ausführen des Kommandos: " . $e->getMessage());

            // Erweiterte Fehlerbehandlung basierend auf Exception-Typ
            if ($e instanceof \InvalidArgumentException) {
                return ExitCode::INVALID_INPUT;
            }

            if ($e instanceof \RuntimeException) {
                return ExitCode::SOFTWARE_ERROR;
            }

            return ExitCode::GENERAL_ERROR;
        }
    }

    private function showHelp(): void
    {
        $this->output->writeLine("Verfügbare Kommandos:", ConsoleColor::BRIGHT_CYAN);
        $this->output->newLine();

        $menu = new InteractiveMenu($this->output);
        $menu->setTitle("Kommandos");

        foreach ($this->commands as $name => $command) {

            $description = $command['description'] ?: 'Keine Beschreibung verfügbar';

            $menu->addItem($name, function () use ($name) {
                return $this->executeCommand($name, [])->value;
            }, $description);
            #$this->output->writeLine(sprintf("  %-20s %s", $name, $description));
        }

        $this->output->writeLine("  " . $menu->showInteractive());

        $this->output->newLine();
        $this->output->writeLine("Verwendung:", ConsoleColor::BRIGHT_YELLOW);
        $this->output->writeLine("  php {$this->scriptName} <kommando> [argumente]");
    }
}
