<?php

declare(strict_types=1);

namespace Tests\Framework\Console;

use App\Framework\Console\ConsoleCommand;
use App\Framework\Console\DemoCommand;
use App\Framework\Discovery\Cache\DiscoveryCacheIdentifiers;
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
use ReflectionClass;
use ReflectionMethod;

describe('ConsoleCommand Attribute Discovery', function () {
    beforeEach(function () {
        // Clear any cached discovery results
        if (function_exists('opcache_reset')) {
            opcache_reset();
        }
    });

    it('can discover ConsoleCommand attributes from DemoCommand class', function () {
        $reflection = new ReflectionClass(DemoCommand::class);
        $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);

        $foundCommands = [];

        foreach ($methods as $method) {
            $attributes = $method->getAttributes(ConsoleCommand::class);

            foreach ($attributes as $attribute) {
                $command = $attribute->newInstance();
                $foundCommands[$command->name] = [
                    'name' => $command->name,
                    'description' => $command->description,
                    'method' => $method->getName(),
                ];
            }
        }

        // Should find all the demo commands
        expect($foundCommands)->toHaveKey('demo:hello');
        expect($foundCommands)->toHaveKey('demo:colors');
        expect($foundCommands)->toHaveKey('demo:interactive');
        expect($foundCommands)->toHaveKey('demo:menu');
        expect($foundCommands)->toHaveKey('demo:simple-menu');
        expect($foundCommands)->toHaveKey('demo:wizard');

        // Verify command details
        expect($foundCommands['demo:hello']['description'])->toBe('Zeigt eine einfache Hallo-Welt-Nachricht');
        expect($foundCommands['demo:hello']['method'])->toBe('hello');

        expect($foundCommands['demo:colors']['description'])->toBe('Zeigt alle verfügbaren Farben');
        expect($foundCommands['demo:colors']['method'])->toBe('colors');

        expect($foundCommands['demo:interactive']['description'])->toBe('Interaktive Demo mit Benutzereingaben');
        expect($foundCommands['demo:interactive']['method'])->toBe('interactive');
    });

    it('correctly parses ConsoleCommand attribute syntax', function () {
        // Test that the correct syntax #[ConsoleCommand] works properly
        $reflection = new ReflectionMethod(DemoCommand::class, 'hello');
        $attributes = $reflection->getAttributes(ConsoleCommand::class);

        expect($attributes)->toHaveCount(1);

        $command = $attributes[0]->newInstance();
        expect($command)->toBeInstanceOf(ConsoleCommand::class);
        expect($command->name)->toBe('demo:hello');
        expect($command->description)->toBe('Zeigt eine einfache Hallo-Welt-Nachricht');
    });

    it('validates that all discovered commands have unique names', function () {
        $reflection = new ReflectionClass(DemoCommand::class);
        $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);

        $commandNames = [];
        $duplicates = [];

        foreach ($methods as $method) {
            $attributes = $method->getAttributes(ConsoleCommand::class);

            foreach ($attributes as $attribute) {
                $command = $attribute->newInstance();

                if (isset($commandNames[$command->name])) {
                    $duplicates[] = $command->name;
                } else {
                    $commandNames[$command->name] = true;
                }
            }
        }

        // Should have no duplicate command names
        expect($duplicates)->toBeEmpty();

        // Should have 6 unique commands
        expect(count($commandNames))->toBe(6);
    });

    it('ensures command names are properly formatted', function () {
        $reflection = new ReflectionClass(DemoCommand::class);
        $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);

        foreach ($methods as $method) {
            $attributes = $method->getAttributes(ConsoleCommand::class);

            foreach ($attributes as $attribute) {
                $command = $attribute->newInstance();

                // Command names should not be empty
                expect($command->name)->not->toBeEmpty();

                // Command names should contain a colon (namespace:command format)
                expect($command->name)->toContain(':');

                // Command names should start with 'demo:'
                expect($command->name)->toStartWith('demo:');

                // Command names should not contain spaces
                expect($command->name)->not->toContain(' ');

                // Command descriptions should not be empty
                expect($command->description)->not->toBeEmpty();
            }
        }
    });

    it('can create attribute instances from discovered attributes', function () {
        $reflection = new ReflectionMethod(DemoCommand::class, 'hello');
        $attributes = $reflection->getAttributes(ConsoleCommand::class);

        $attribute = $attributes[0];
        $command = $attribute->newInstance();

        expect($command)->toBeInstanceOf(ConsoleCommand::class);
        expect($command->name)->toBe('demo:hello');
        expect($command->description)->toBe('Zeigt eine einfache Hallo-Welt-Nachricht');
    });

    it('handles edge cases in command discovery', function () {
        // Test that methods without ConsoleCommand attributes are ignored
        $reflection = new ReflectionClass(DemoCommand::class);
        $userMenuMethod = $reflection->getMethod('userMenu');

        $attributes = $userMenuMethod->getAttributes(ConsoleCommand::class);
        expect($attributes)->toBeEmpty();
    });

    it('validates that discovered commands have public methods', function () {
        $reflection = new ReflectionClass(DemoCommand::class);
        $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);

        foreach ($methods as $method) {
            $attributes = $method->getAttributes(ConsoleCommand::class);

            if (! empty($attributes)) {
                expect($method->isPublic())->toBeTrue();
                expect($method->isStatic())->toBeFalse();
            }
        }
    });
});

describe('ConsoleCommand Attribute Integration with Discovery System', function () {
    it('can be discovered through DiscoveredAttribute system', function () {
        // Create a mock DiscoveredAttribute for a ConsoleCommand
        $className = \App\Framework\Core\ValueObjects\ClassName::create(DemoCommand::class);
        $methodName = \App\Framework\Core\ValueObjects\MethodName::create('hello');

        $discoveredAttribute = new DiscoveredAttribute(
            attributeClass: ConsoleCommand::class,
            className: $className,
            methodName: $methodName,
            arguments: ['demo:hello', 'Zeigt eine einfache Hallo-Welt-Nachricht'],
            target: \App\Framework\Discovery\ValueObjects\AttributeTarget::METHOD
        );

        // Test that we can create the attribute instance
        $command = $discoveredAttribute->createAttributeInstance();

        expect($command)->toBeInstanceOf(ConsoleCommand::class);
        expect($command->name)->toBe('demo:hello');
        expect($command->description)->toBe('Zeigt eine einfache Hallo-Welt-Nachricht');
    });

    it('properly handles attribute target validation', function () {
        // ConsoleCommand attributes should only be on methods
        $className = \App\Framework\Core\ValueObjects\ClassName::create(DemoCommand::class);
        $methodName = \App\Framework\Core\ValueObjects\MethodName::create('hello');

        $discoveredAttribute = new DiscoveredAttribute(
            attributeClass: ConsoleCommand::class,
            className: $className,
            methodName: $methodName,
            arguments: ['demo:hello', 'Test description'],
            target: \App\Framework\Discovery\ValueObjects\AttributeTarget::METHOD
        );

        expect($discoveredAttribute->target)->toBe(\App\Framework\Discovery\ValueObjects\AttributeTarget::METHOD);
    });
});

describe('ConsoleCommand Discovery Cache Behavior', function () {
    it('generates context-specific cache keys', function () {
        $paths = ['/test/path'];

        // Test different execution contexts generate different cache keys
        $consoleKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, 'console');
        $cliScriptKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, 'cli-script');
        $webKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, 'web');

        expect($consoleKey)->not->toBe($cliScriptKey);
        expect($consoleKey)->not->toBe($webKey);
        expect($cliScriptKey)->not->toBe($webKey);

        // All should be valid cache key format
        expect($consoleKey->toString())->toMatch('/^[a-zA-Z0-9_\-:\.]+$/');
        expect($cliScriptKey->toString())->toMatch('/^[a-zA-Z0-9_\-:\.]+$/');
        expect($webKey->toString())->toMatch('/^[a-zA-Z0-9_\-:\.]+$/');
    });

    it('cache keys include execution context information', function () {
        $paths = ['/test/path'];
        $context = 'console';

        $cacheKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, $context);

        // Cache key should include context information
        expect($cacheKey->toString())->toContain($context);
    });
});
