<?php

declare(strict_types=1);

namespace Tests\Framework\Console;

use App\Framework\Cache\Cache;
use App\Framework\Cache\Driver\InMemoryCache;
use App\Framework\Cache\GeneralCache;
use App\Framework\Config\AppConfig;
use App\Framework\Console\ConsoleApplication;
use App\Framework\Console\DemoCommand;
use App\Framework\Console\ExitCode;
use App\Framework\Context\ExecutionContext;
use App\Framework\Core\PathProvider;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\SystemClock;
use App\Framework\DateTime\Timezone;
use App\Framework\DI\Container;
use App\Framework\DI\DefaultContainer;
use App\Framework\Discovery\InitializerProcessor;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Reflection\CachedReflectionProvider;
use App\Framework\Reflection\ReflectionProvider;
use App\Framework\Serializer\Php\PhpSerializer;
use App\Framework\Serializer\Php\PhpSerializerConfig;

describe('ConsoleApplication Integration', function () {
    beforeEach(function () {
        // Create fresh container and dependencies for each test
        $this->container = new DefaultContainer();
        $cacheDriver = new InMemoryCache();
        $serializer = new PhpSerializer(PhpSerializerConfig::safe());
        $this->cache = new GeneralCache($cacheDriver, $serializer);
        $this->clock = new SystemClock();
        $this->pathProvider = new PathProvider('/home/michael/dev/michaelschiemer');

        // Register required dependencies
        $this->container->singleton(Cache::class, $this->cache);
        $this->container->singleton(Clock::class, $this->clock);
        $this->container->singleton(PathProvider::class, $this->pathProvider);

        // Register ReflectionProvider and ExecutionContext dependencies
        $reflectionProvider = new CachedReflectionProvider();
        $executionContext = ExecutionContext::detect();
        $this->container->singleton(ReflectionProvider::class, $reflectionProvider);
        $this->container->singleton(ExecutionContext::class, $executionContext);

        $this->container->singleton(InitializerProcessor::class, fn ($c) => new InitializerProcessor(
            $c,
            $c->get(ReflectionProvider::class),
            $c->get(ExecutionContext::class)
        ));

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

        // Create a simple AppConfig for testing
        $appConfig = new AppConfig(
            environment: 'testing',
            debug: true,
            timezone: Timezone::UTC,
            locale: 'en'
        );
        $this->container->singleton(AppConfig::class, $appConfig);

        // Clear cache
        $this->cache->clear();

        // Create a test output to capture console 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 writeError(string $message): void
            {
                $this->capturedLines[] = "ERROR: $message";
            }

            public function writeSuccess(string $message): void
            {
                $this->capturedLines[] = "SUCCESS: $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('properly initializes CommandRegistry with discovery', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Should not throw exception during initialization
        expect($app)->toBeInstanceOf(ConsoleApplication::class);

        // Initialization should complete successfully (no exceptions thrown)
        // The fact that we got here means discovery and command registry setup worked
    });

    it('handles empty command list with fallback discovery', function () {
        // First create a scenario where no commands would be found initially
        $emptyRegistry = new DiscoveryRegistry(
            attributes: new \App\Framework\Discovery\Results\AttributeRegistry([]),
            interfaces: new \App\Framework\Discovery\Results\InterfaceRegistry([]),
            templates: new \App\Framework\Discovery\Results\TemplateRegistry([])
        );

        // Register the empty registry initially
        $this->container->singleton(DiscoveryRegistry::class, $emptyRegistry);

        // ConsoleApplication should trigger fallback discovery
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Should have attempted fallback discovery
        $loggedOutput = implode(' ', $this->output->capturedLines);

        // Either it found commands through fallback or handled the empty case gracefully
        expect($app)->toBeInstanceOf(ConsoleApplication::class);
    });

    it('can execute demo commands successfully', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Test demo:hello command
        $argv = ['test-console', 'demo:hello', 'TestName'];
        $exitCode = $app->run($argv);

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

        // Should have captured the hello output
        $output = implode(' ', $this->output->capturedLines);
        expect($output)->toContain('Hallo, TestName!');
    });

    it('shows help when no command is provided', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Run with no command
        $argv = ['test-console'];
        $exitCode = $app->run($argv);

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

        // Should show help/available commands
        $output = implode(' ', $this->output->capturedLines);
        expect($output)->toContain('Verfügbare Kommandos');
    });

    it('handles unknown commands gracefully', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Try to run non-existent command
        $argv = ['test-console', 'nonexistent:command'];
        $exitCode = $app->run($argv);

        expect($exitCode)->toBe(ExitCode::COMMAND_NOT_FOUND->value);

        // Should show error message
        $output = implode(' ', $this->output->capturedLines);
        expect($output)->toContain('nicht gefunden');
    });

    it('suggests similar commands for typos', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Try command with typo (demo:helo instead of demo:hello)
        $argv = ['test-console', 'demo:helo'];
        $exitCode = $app->run($argv);

        expect($exitCode)->toBe(ExitCode::COMMAND_NOT_FOUND->value);

        // Should suggest similar commands
        $output = implode(' ', $this->output->capturedLines);
        expect($output)->toContain('Meinten Sie vielleicht');
    });

    it('handles help command', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Test various help invocations
        $helpCommands = ['help', '--help', '-h'];

        foreach ($helpCommands as $helpCmd) {
            $this->output->capturedLines = []; // Clear previous output

            $argv = ['test-console', $helpCmd];
            $exitCode = $app->run($argv);

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

            $output = implode(' ', $this->output->capturedLines);
            expect($output)->toContain('Verfügbare Kommandos');
        }
    });

    it('finds expected number of commands after discovery', function () {
        // Force fresh discovery by clearing cache
        $this->cache->clear();

        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Run help to see available commands
        $argv = ['test-console', 'help'];
        $app->run($argv);

        $output = implode(' ', $this->output->capturedLines);

        // Should find demo commands
        expect($output)->toContain('demo:hello');
        expect($output)->toContain('demo:colors');
        expect($output)->toContain('demo:interactive');

        // Should not show "Keine Kommandos verfügbar"
        expect($output)->not->toContain('Keine Kommandos verfügbar');
    });

    it('validates and sanitizes input arguments', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Test with various argument types
        $argv = ['test-console', 'demo:hello', 'ValidName', '--option=value'];
        $exitCode = $app->run($argv);

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

        // Should have processed arguments without error
        $output = implode(' ', $this->output->capturedLines);
        expect($output)->toContain('Hallo, ValidName!');
    });

    it('handles empty argv gracefully', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Test with empty argv (should not happen in practice, but test robustness)
        try {
            $exitCode = $app->run([]);
            // If it doesn't throw, should return an error code
            expect($exitCode)->toBeInt();
        } catch (\InvalidArgumentException $e) {
            // Expected behavior for invalid input
            expect($e->getMessage())->toContain('No arguments provided');
        }
    });

    it('shows command usage for invalid arguments', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // This test depends on actual command implementation
        // For now, just ensure the app can handle commands
        $argv = ['test-console', 'demo:hello'];
        $exitCode = $app->run($argv);

        // Should succeed or fail gracefully
        expect($exitCode)->toBeInt();
        expect($exitCode)->toBeGreaterThanOrEqual(0);
        expect($exitCode)->toBeLessThan(256);
    });
});

describe('ConsoleApplication Error Handling', function () {
    beforeEach(function () {
        $this->container = new DefaultContainer();
        $cacheDriver = new InMemoryCache();
        $serializer = new PhpSerializer(PhpSerializerConfig::safe());
        $this->cache = new GeneralCache($cacheDriver, $serializer);
        $this->clock = new SystemClock();
        $this->pathProvider = new PathProvider('/home/michael/dev/michaelschiemer');

        $this->container->singleton(Cache::class, $this->cache);
        $this->container->singleton(Clock::class, $this->clock);
        $this->container->singleton(PathProvider::class, $this->pathProvider);

        // Register ReflectionProvider and ExecutionContext dependencies
        $reflectionProvider = new CachedReflectionProvider();
        $executionContext = ExecutionContext::detect();
        $this->container->singleton(ReflectionProvider::class, $reflectionProvider);
        $this->container->singleton(ExecutionContext::class, $executionContext);

        $this->container->singleton(InitializerProcessor::class, fn ($c) => new InitializerProcessor(
            $c,
            $c->get(ReflectionProvider::class),
            $c->get(ExecutionContext::class)
        ));
        $this->container->singleton(DemoCommand::class, fn () => new DemoCommand());

        $appConfig = new AppConfig(
            environment: 'testing',
            debug: true,
            timezone: Timezone::UTC,
            locale: 'en'
        );
        $this->container->singleton(AppConfig::class, $appConfig);

        $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 writeError(string $message): void
            {
                $this->capturedLines[] = "ERROR: $message";
            }

            public function writeSuccess(string $message): void
            {
                $this->capturedLines[] = "SUCCESS: $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 framework exceptions gracefully', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Test with valid command to ensure basic functionality
        $argv = ['test-console', 'demo:hello'];
        $exitCode = $app->run($argv);

        // Should handle any framework exceptions gracefully
        expect($exitCode)->toBeInt();
        expect($exitCode)->toBeGreaterThanOrEqual(0);
    });

    it('provides appropriate exit codes for different error types', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Test command not found
        $argv = ['test-console', 'nonexistent:command'];
        $exitCode = $app->run($argv);
        expect($exitCode)->toBe(ExitCode::COMMAND_NOT_FOUND->value);

        // Test successful command
        $argv = ['test-console', 'demo:hello'];
        $exitCode = $app->run($argv);
        expect($exitCode)->toBe(ExitCode::SUCCESS->value);
    });

    it('shows debug information in development mode', function () {
        // AppConfig is already set to debug mode in beforeEach
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Test with any command
        $argv = ['test-console', 'demo:hello'];
        $exitCode = $app->run($argv);

        // Should complete without throwing exceptions
        expect($exitCode)->toBeInt();
    });

    it('handles malformed arguments gracefully', function () {
        $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output);

        // Test with various potentially problematic arguments
        $testCases = [
            ['test-console', 'demo:hello', ''],  // Empty argument
            ['test-console', 'demo:hello', 'normal-arg'],  // Normal case
        ];

        foreach ($testCases as $argv) {
            $this->output->capturedLines = [];

            $exitCode = $app->run($argv);

            // Should not crash, return valid exit code
            expect($exitCode)->toBeInt();
            expect($exitCode)->toBeGreaterThanOrEqual(0);
            expect($exitCode)->toBeLessThan(256);
        }
    });
});
