<?php

declare(strict_types=1);

namespace Tests\Framework\Console;

use App\Framework\Console\CommandList;
use App\Framework\Console\CommandRegistry;
use App\Framework\Console\ConsoleCommand;
use App\Framework\Console\DemoCommand;
use App\Framework\Console\ExitCode;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\MethodName;
use App\Framework\DI\Container;
use App\Framework\DI\DefaultContainer;
use App\Framework\Discovery\Results\AttributeRegistry;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\Results\InterfaceRegistry;
use App\Framework\Discovery\Results\TemplateRegistry;
use App\Framework\Discovery\ValueObjects\AttributeTarget;
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
use App\Framework\Exception\FrameworkException;

describe('CommandRegistry', function () {
    beforeEach(function () {
        // Create fresh container for each test
        $this->container = new DefaultContainer();

        // Register DemoCommand in container
        $this->container->singleton(DemoCommand::class, fn () => new DemoCommand());

        // Create a mock DiscoveryRegistry with console commands
        $this->discoveredAttributes = [
            new DiscoveredAttribute(
                attributeClass: ConsoleCommand::class,
                className: ClassName::create(DemoCommand::class),
                methodName: MethodName::create('hello'),
                arguments: ['demo:hello', 'Zeigt eine einfache Hallo-Welt-Nachricht'],
                target: AttributeTarget::METHOD
            ),
            new DiscoveredAttribute(
                attributeClass: ConsoleCommand::class,
                className: ClassName::create(DemoCommand::class),
                methodName: MethodName::create('colors'),
                arguments: ['demo:colors', 'Zeigt alle verfügbaren Farben'],
                target: AttributeTarget::METHOD
            ),
            new DiscoveredAttribute(
                attributeClass: ConsoleCommand::class,
                className: ClassName::create(DemoCommand::class),
                methodName: MethodName::create('interactive'),
                arguments: ['demo:interactive', 'Interaktive Demo mit Benutzereingaben'],
                target: AttributeTarget::METHOD
            ),
        ];

        $attributeRegistry = new AttributeRegistry();
        foreach ($this->discoveredAttributes as $attribute) {
            $attributeRegistry->add(ConsoleCommand::class, $attribute);
        }

        $this->discoveryRegistry = new DiscoveryRegistry(
            attributes: $attributeRegistry,
            interfaces: new InterfaceRegistry([]),
            templates: new TemplateRegistry([])
        );

        // Create test output
        $this->output = new class () implements \App\Framework\Console\ConsoleOutputInterface {
            public array $capturedLines = [];

            public function write(string $message, null|\App\Framework\Console\ConsoleColor|\App\Framework\Console\ConsoleStyle $style = null): void
            {
                $this->capturedLines[] = $message;
            }

            public function writeLine(string $message = '', ?\App\Framework\Console\ConsoleColor $color = null): void
            {
                $this->capturedLines[] = $message;
            }

            public function writeSuccess(string $message): void
            {
                $this->capturedLines[] = "SUCCESS: $message";
            }

            public function writeError(string $message): void
            {
                $this->capturedLines[] = "ERROR: $message";
            }

            public function writeWarning(string $message): void
            {
                $this->capturedLines[] = "WARNING: $message";
            }

            public function writeInfo(string $message): void
            {
                $this->capturedLines[] = "INFO: $message";
            }

            public function newLine(int $count = 1): void
            {
                for ($i = 0; $i < $count; $i++) {
                    $this->capturedLines[] = '';
                }
            }

            public function askQuestion(string $question, ?string $default = null): string
            {
                return $default ?? '';
            }

            public function confirm(string $question, bool $default = false): bool
            {
                return $default;
            }

            public function writeWindowTitle(string $title, int $mode = 0): void
            {
                // No-op for testing
            }
        };
    });

    // Helper function to create DiscoveryRegistry with attributes
    function createRegistryWithAttributes(array $attributes): DiscoveryRegistry
    {
        $attributeRegistry = new AttributeRegistry();
        foreach ($attributes as $attribute) {
            $attributeRegistry->add(ConsoleCommand::class, $attribute);
        }

        return new DiscoveryRegistry(
            attributes: $attributeRegistry,
            interfaces: new InterfaceRegistry([]),
            templates: new TemplateRegistry([])
        );
    }

    it('creates CommandRegistry with DiscoveryRegistry', function () {
        $registry = new CommandRegistry($this->container, $this->discoveryRegistry);

        expect($registry)->toBeInstanceOf(CommandRegistry::class);

        $commandList = $registry->getCommandList();
        expect($commandList)->toBeInstanceOf(CommandList::class);


        expect($commandList->count())->toBe(3);
    });

    it('discovers all demo commands correctly', function () {
        $registry = new CommandRegistry($this->container, $this->discoveryRegistry);
        $commandList = $registry->getCommandList();

        // Should have all demo commands
        expect($commandList->has('demo:hello'))->toBeTrue();
        expect($commandList->has('demo:colors'))->toBeTrue();
        expect($commandList->has('demo:interactive'))->toBeTrue();

        // Check command details
        $helloCommand = $commandList->get('demo:hello');
        expect($helloCommand->name)->toBe('demo:hello');
        expect($helloCommand->description)->toBe('Zeigt eine einfache Hallo-Welt-Nachricht');
    });

    it('stores discovered attributes for commands', function () {
        $registry = new CommandRegistry($this->container, $this->discoveryRegistry);

        // Should be able to get discovered attribute for each command
        $discoveredAttribute = $registry->getDiscoveredAttribute('demo:hello');
        expect($discoveredAttribute)->toBeInstanceOf(DiscoveredAttribute::class);
        expect($discoveredAttribute->className->getFullyQualified())->toBe(DemoCommand::class);
        expect($discoveredAttribute->methodName->toString())->toBe('hello');

        $discoveredAttribute = $registry->getDiscoveredAttribute('demo:colors');
        expect($discoveredAttribute->className->getFullyQualified())->toBe(DemoCommand::class);
        expect($discoveredAttribute->methodName->toString())->toBe('colors');
    });

    it('throws exception for unknown command attribute', function () {
        $registry = new CommandRegistry($this->container, $this->discoveryRegistry);

        expect(fn () => $registry->getDiscoveredAttribute('unknown:command'))
            ->toThrow(FrameworkException::class, 'No discovered attribute found for command');
    });

    it('can execute demo commands', function () {
        $registry = new CommandRegistry($this->container, $this->discoveryRegistry);

        // Execute demo:hello command
        $exitCode = $registry->executeCommand('demo:hello', ['TestName'], $this->output);

        expect($exitCode)->toBe(ExitCode::SUCCESS);

        // Check output was captured
        $output = implode(' ', $this->output->capturedLines);
        expect($output)->toContain('Hallo, TestName!');
    });

    it('executes commands with proper argument handling', function () {
        $registry = new CommandRegistry($this->container, $this->discoveryRegistry);

        // Execute command with different arguments
        $exitCode = $registry->executeCommand('demo:hello', [], $this->output);
        expect($exitCode)->toBe(ExitCode::SUCCESS);

        // Default name should be used when no argument provided
        $output = implode(' ', $this->output->capturedLines);
        expect($output)->toContain('Hallo, Welt!');
    });

    it('handles command execution errors gracefully', function () {
        // Create registry with invalid command setup
        $invalidDiscoveredAttribute = new DiscoveredAttribute(
            attributeClass: ConsoleCommand::class,
            className: ClassName::create('NonExistentClass'),
            methodName: MethodName::create('invalidMethod'),
            arguments: ['invalid:command', 'Invalid command'],
            target: AttributeTarget::METHOD
        );

        $invalidRegistry = createRegistryWithAttributes([$invalidDiscoveredAttribute]);

        $registry = new CommandRegistry($this->container, $invalidRegistry);

        expect(fn () => $registry->executeCommand('invalid:command', [], $this->output))
            ->toThrow(FrameworkException::class);
    });

    it('validates command structure during registration', function () {
        // Create a command with invalid method
        $invalidMethodAttribute = new DiscoveredAttribute(
            attributeClass: ConsoleCommand::class,
            className: ClassName::create(DemoCommand::class),
            methodName: MethodName::create('nonExistentMethod'),
            arguments: ['demo:invalid', 'Invalid method'],
            target: AttributeTarget::METHOD
        );

        $registryWithInvalid = createRegistryWithAttributes([$invalidMethodAttribute]);

        // Should handle invalid commands gracefully during registration
        $registry = new CommandRegistry($this->container, $registryWithInvalid);
        $commandList = $registry->getCommandList();

        // Invalid command should be filtered out
        expect($commandList->has('demo:invalid'))->toBeFalse();
    });

    it('normalizes command return values to ExitCode', function () {
        $registry = new CommandRegistry($this->container, $this->discoveryRegistry);

        // Demo commands return int 0, should be converted to ExitCode::SUCCESS
        $exitCode = $registry->executeCommand('demo:hello', [], $this->output);

        expect($exitCode)->toBeInstanceOf(ExitCode::class);
        expect($exitCode)->toBe(ExitCode::SUCCESS);
    });

    it('logs warnings for long-running commands', function () {
        // This test would require a command that takes longer than 30 seconds
        // For now, just verify the mechanism exists
        $registry = new CommandRegistry($this->container, $this->discoveryRegistry);

        // Execute a normal command (should complete quickly)
        $startTime = microtime(true);
        $exitCode = $registry->executeCommand('demo:hello', [], $this->output);
        $endTime = microtime(true);

        expect($exitCode)->toBe(ExitCode::SUCCESS);
        expect($endTime - $startTime)->toBeLessThan(1.0); // Should be fast
    });

    it('handles empty discovery registry gracefully', function () {
        $emptyRegistry = createRegistryWithAttributes([]);

        $registry = new CommandRegistry($this->container, $emptyRegistry);
        $commandList = $registry->getCommandList();

        expect($commandList->count())->toBe(0);
        expect($commandList->isEmpty())->toBeTrue();
    });

    it('prevents duplicate command names', function () {
        // Create duplicate commands
        $duplicateAttributes = [
            new DiscoveredAttribute(
                attributeClass: ConsoleCommand::class,
                className: ClassName::create(DemoCommand::class),
                methodName: MethodName::create('hello'),
                arguments: ['duplicate:name', 'First command'],
                target: AttributeTarget::METHOD
            ),
            new DiscoveredAttribute(
                attributeClass: ConsoleCommand::class,
                className: ClassName::create(DemoCommand::class),
                methodName: MethodName::create('colors'),
                arguments: ['duplicate:name', 'Second command'],
                target: AttributeTarget::METHOD
            ),
        ];

        $duplicateRegistry = createRegistryWithAttributes($duplicateAttributes);

        // Should throw exception for duplicate command names
        expect(fn () => new CommandRegistry($this->container, $duplicateRegistry))
            ->toThrow(FrameworkException::class, "Duplicate command name 'duplicate:name'");
    });
});

describe('CommandRegistry Error Scenarios', function () {
    beforeEach(function () {
        $this->container = new DefaultContainer();
        $this->container->singleton(DemoCommand::class, fn () => new DemoCommand());

        $this->output = new class () implements \App\Framework\Console\ConsoleOutputInterface {
            public array $capturedLines = [];

            public function write(string $message, null|\App\Framework\Console\ConsoleColor|\App\Framework\Console\ConsoleStyle $style = null): void
            {
                $this->capturedLines[] = $message;
            }

            public function writeLine(string $message = '', ?\App\Framework\Console\ConsoleColor $color = null): void
            {
                $this->capturedLines[] = $message;
            }

            public function writeSuccess(string $message): void
            {
                $this->capturedLines[] = "SUCCESS: $message";
            }

            public function writeError(string $message): void
            {
                $this->capturedLines[] = "ERROR: $message";
            }

            public function writeWarning(string $message): void
            {
                $this->capturedLines[] = "WARNING: $message";
            }

            public function writeInfo(string $message): void
            {
                $this->capturedLines[] = "INFO: $message";
            }

            public function newLine(int $count = 1): void
            {
                for ($i = 0; $i < $count; $i++) {
                    $this->capturedLines[] = '';
                }
            }

            public function askQuestion(string $question, ?string $default = null): string
            {
                return $default ?? '';
            }

            public function confirm(string $question, bool $default = false): bool
            {
                return $default;
            }

            public function writeWindowTitle(string $title, int $mode = 0): void
            {
                // No-op for testing
            }
        };
    });

    it('handles missing class gracefully', function () {
        $invalidAttribute = new DiscoveredAttribute(
            attributeClass: ConsoleCommand::class,
            className: ClassName::create('App\\NonExistent\\Class'),
            methodName: MethodName::create('method'),
            arguments: ['test:command', 'Test command'],
            target: AttributeTarget::METHOD
        );

        $registry = createRegistryWithAttributes([$invalidAttribute]);

        // Should handle missing class gracefully
        $commandRegistry = new CommandRegistry($this->container, $registry);
        $commandList = $commandRegistry->getCommandList();

        // Command with missing class should be filtered out
        expect($commandList->has('test:command'))->toBeFalse();
    });

    it('handles empty command names gracefully', function () {
        $emptyNameAttribute = new DiscoveredAttribute(
            attributeClass: ConsoleCommand::class,
            className: ClassName::create(DemoCommand::class),
            methodName: MethodName::create('hello'),
            arguments: ['', 'Empty name command'],
            target: AttributeTarget::METHOD
        );

        $registry = createRegistryWithAttributes([$emptyNameAttribute]);

        // Should handle empty command names gracefully
        $commandRegistry = new CommandRegistry($this->container, $registry);
        $commandList = $commandRegistry->getCommandList();

        // Command with empty name should be filtered out
        expect($commandList->count())->toBe(0);
    });

    it('continues registration despite individual command failures', function () {
        $attributes = [
            // Valid command
            new DiscoveredAttribute(
                attributeClass: ConsoleCommand::class,
                className: ClassName::create(DemoCommand::class),
                methodName: MethodName::create('hello'),
                arguments: ['demo:hello', 'Valid command'],
                target: AttributeTarget::METHOD
            ),
            // Invalid command (missing class)
            new DiscoveredAttribute(
                attributeClass: ConsoleCommand::class,
                className: ClassName::create('NonExistent\\Class'),
                methodName: MethodName::create('method'),
                arguments: ['invalid:command', 'Invalid command'],
                target: AttributeTarget::METHOD
            ),
            // Another valid command
            new DiscoveredAttribute(
                attributeClass: ConsoleCommand::class,
                className: ClassName::create(DemoCommand::class),
                methodName: MethodName::create('colors'),
                arguments: ['demo:colors', 'Another valid command'],
                target: AttributeTarget::METHOD
            ),
        ];

        $registry = createRegistryWithAttributes($attributes);

        $commandRegistry = new CommandRegistry($this->container, $registry);
        $commandList = $commandRegistry->getCommandList();

        // Should have registered the valid commands despite the invalid one
        expect($commandList->count())->toBe(2);
        expect($commandList->has('demo:hello'))->toBeTrue();
        expect($commandList->has('demo:colors'))->toBeTrue();
        expect($commandList->has('invalid:command'))->toBeFalse();
    });
});
