<?php

namespace MintyPHP\Tests\Console;

use MintyPHP\Console\Commands\Module\ValidateCommand;
use PHPUnit\Framework\TestCase;

/**
 * Tests for module:validate enhancements:
 * - AuthorizationPolicy container-registration check (check 20)
 * - Event listener unknown event warning (check 21)
 *
 * Uses reflection to call the private validateModule() method directly,
 * avoiding bootstrapApp() and STDOUT capture issues.
 */
final class ValidateCommandTest extends TestCase
{
    private string $modulesDir;

    /** @var list<string> Module IDs to clean up */
    private array $cleanup = [];

    private \ReflectionMethod $validateModule;
    private ValidateCommand $command;

    protected function setUp(): void
    {
        $this->modulesDir = dirname(__DIR__, 2) . '/modules';
        $this->command = new ValidateCommand();
        $this->validateModule = new \ReflectionMethod($this->command, 'validateModule');
    }

    protected function tearDown(): void
    {
        foreach ($this->cleanup as $id) {
            $this->removeDir($this->modulesDir . '/' . $id);
        }
    }

    public function testValidateWarnsOnUnknownEventListenerKey(): void
    {
        $id = 'ztest-unknown-event';
        $this->scaffoldModule($id, <<<'PHP'
        <?php
        return [
            'id' => 'ztest-unknown-event',
            'version' => '0.1.0',
            'event_listeners' => [
                'user.created' => [
                    ['class' => 'NonExistent\Listener', 'method' => 'handle'],
                ],
                'totally.unknown.event' => [
                    ['class' => 'NonExistent\Listener', 'method' => 'handle'],
                ],
            ],
        ];
        PHP);

        [$errors, $warnings] = $this->validateModule->invoke($this->command, $this->modulesDir, $id);

        $warningText = implode("\n", $warnings);
        self::assertStringContainsString('totally.unknown.event', $warningText);
        self::assertStringContainsString('not defined in ModuleEvents', $warningText);
        // user.created is a known event — should NOT trigger a warning
        self::assertStringNotContainsString("unknown event 'user.created'", $warningText);
    }

    public function testValidateDoesNotWarnOnKnownEventKeys(): void
    {
        $id = 'ztest-known-events';
        $this->scaffoldModule($id, <<<'PHP'
        <?php
        return [
            'id' => 'ztest-known-events',
            'version' => '0.1.0',
            'event_listeners' => [
                'user.created' => [
                    ['class' => 'NonExistent\Listener', 'method' => 'handle'],
                ],
                'scheduler.job_failed' => [
                    ['class' => 'NonExistent\Listener', 'method' => 'handle'],
                ],
            ],
        ];
        PHP);

        [$errors, $warnings] = $this->validateModule->invoke($this->command, $this->modulesDir, $id);

        $warningText = implode("\n", $warnings);
        self::assertStringNotContainsString('not defined in ModuleEvents', $warningText);
    }

    public function testValidateWarnsWhenPolicyNotReferencedInRegistrar(): void
    {
        $id = 'ztest-policy-missing';
        $moduleDir = $this->modulesDir . '/' . $id;
        mkdir($moduleDir . '/lib/Module/ZtestPolicyMissing', 0777, true);
        $this->cleanup[] = $id;

        file_put_contents(
            $moduleDir . '/lib/Module/ZtestPolicyMissing/ZtestPolicyMissingContainerRegistrar.php',
            <<<'PHP'
            <?php
            namespace MintyPHP\Module\ZtestPolicyMissing;
            use MintyPHP\App\AppContainer;
            use MintyPHP\App\Container\ContainerRegistrar;
            final class ZtestPolicyMissingContainerRegistrar implements ContainerRegistrar {
                public function register(AppContainer $container): void {}
            }
            PHP
        );

        file_put_contents(
            $moduleDir . '/lib/Module/ZtestPolicyMissing/ZtestPolicyMissingAuthorizationPolicy.php',
            <<<'PHP'
            <?php
            namespace MintyPHP\Module\ZtestPolicyMissing;
            use MintyPHP\Service\Access\AuthorizationDecision;
            use MintyPHP\Service\Access\AuthorizationPolicyInterface;
            final class ZtestPolicyMissingAuthorizationPolicy implements AuthorizationPolicyInterface {
                public function supports(string $ability): bool { return false; }
                public function authorize(string $ability, array $context = []): AuthorizationDecision {
                    return AuthorizationDecision::deny(403, 'forbidden');
                }
            }
            PHP
        );

        file_put_contents($moduleDir . '/module.php', <<<'PHP'
        <?php
        return [
            'id' => 'ztest-policy-missing',
            'version' => '0.1.0',
            'container_registrars' => [\MintyPHP\Module\ZtestPolicyMissing\ZtestPolicyMissingContainerRegistrar::class],
            'authorization_policies' => [\MintyPHP\Module\ZtestPolicyMissing\ZtestPolicyMissingAuthorizationPolicy::class],
        ];
        PHP);

        [$errors, $warnings] = $this->validateModule->invoke($this->command, $this->modulesDir, $id);

        $warningText = implode("\n", $warnings);
        self::assertStringContainsString('ZtestPolicyMissingAuthorizationPolicy', $warningText);
        self::assertStringContainsString('not referenced in any ContainerRegistrar', $warningText);
    }

    public function testValidateNoWarningWhenPolicyReferencedInRegistrar(): void
    {
        $id = 'ztest-policy-ok';
        $moduleDir = $this->modulesDir . '/' . $id;
        mkdir($moduleDir . '/lib/Module/ZtestPolicyOk', 0777, true);
        $this->cleanup[] = $id;

        file_put_contents(
            $moduleDir . '/lib/Module/ZtestPolicyOk/ZtestPolicyOkContainerRegistrar.php',
            <<<'PHP'
            <?php
            namespace MintyPHP\Module\ZtestPolicyOk;
            use MintyPHP\App\AppContainer;
            use MintyPHP\App\Container\ContainerRegistrar;
            final class ZtestPolicyOkContainerRegistrar implements ContainerRegistrar {
                public function register(AppContainer $container): void {
                    $container->set(ZtestPolicyOkAuthorizationPolicy::class, static fn () => new ZtestPolicyOkAuthorizationPolicy());
                }
            }
            PHP
        );

        file_put_contents(
            $moduleDir . '/lib/Module/ZtestPolicyOk/ZtestPolicyOkAuthorizationPolicy.php',
            <<<'PHP'
            <?php
            namespace MintyPHP\Module\ZtestPolicyOk;
            use MintyPHP\Service\Access\AuthorizationDecision;
            use MintyPHP\Service\Access\AuthorizationPolicyInterface;
            final class ZtestPolicyOkAuthorizationPolicy implements AuthorizationPolicyInterface {
                public function supports(string $ability): bool { return false; }
                public function authorize(string $ability, array $context = []): AuthorizationDecision {
                    return AuthorizationDecision::deny(403, 'forbidden');
                }
            }
            PHP
        );

        file_put_contents($moduleDir . '/module.php', <<<'PHP'
        <?php
        return [
            'id' => 'ztest-policy-ok',
            'version' => '0.1.0',
            'container_registrars' => [\MintyPHP\Module\ZtestPolicyOk\ZtestPolicyOkContainerRegistrar::class],
            'authorization_policies' => [\MintyPHP\Module\ZtestPolicyOk\ZtestPolicyOkAuthorizationPolicy::class],
        ];
        PHP);

        [$errors, $warnings] = $this->validateModule->invoke($this->command, $this->modulesDir, $id);

        self::assertSame([], $errors);
        $warningText = implode("\n", $warnings);
        self::assertStringNotContainsString('not referenced', $warningText);
    }

    private function scaffoldModule(string $id, string $manifestContent): void
    {
        $moduleDir = $this->modulesDir . '/' . $id;
        mkdir($moduleDir . '/lib', 0777, true);
        file_put_contents($moduleDir . '/module.php', $manifestContent);
        $this->cleanup[] = $id;
    }

    private function removeDir(string $dir): void
    {
        if (!is_dir($dir)) {
            return;
        }
        $items = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::CHILD_FIRST
        );
        foreach ($items as $item) {
            if ($item->isDir()) {
                rmdir($item->getPathname());
            } else {
                unlink($item->getPathname());
            }
        }
        rmdir($dir);
    }
}
