<?php

namespace MintyPHP\Tests\Console;

use MintyPHP\Console\Command;
use MintyPHP\Console\ConsoleApplication;
use PHPUnit\Framework\TestCase;

final class ConsoleApplicationTest extends TestCase
{
    public function testListReturnsZero(): void
    {
        $app = new ConsoleApplication();
        $app->register(new DummyCommand());

        $exitCode = $app->run(['bin/console', 'list']);

        self::assertSame(0, $exitCode);
    }

    public function testEmptyArgvShowsListAndReturnsZero(): void
    {
        $app = new ConsoleApplication();

        $exitCode = $app->run(['bin/console']);

        self::assertSame(0, $exitCode);
    }

    public function testUnknownCommandReturnsOne(): void
    {
        $app = new ConsoleApplication();

        $exitCode = $app->run(['bin/console', 'nonexistent']);

        self::assertSame(1, $exitCode);
    }

    public function testDispatchesRegisteredCommand(): void
    {
        $command = new DummyCommand();
        $app = new ConsoleApplication();
        $app->register($command);

        $exitCode = $app->run(['bin/console', 'test:dummy', 'arg1', '--flag', '--key=value']);

        self::assertSame(0, $exitCode);
        self::assertSame(['arg1'], $command->lastArgs);
        self::assertSame(['flag' => true, 'key' => 'value'], $command->lastOptions);
    }

    public function testCommandExitCodePropagates(): void
    {
        $app = new ConsoleApplication();
        $app->register(new FailingCommand());

        $exitCode = $app->run(['bin/console', 'test:fail']);

        self::assertSame(42, $exitCode);
    }

    public function testMultiplePositionalArgs(): void
    {
        $command = new DummyCommand();
        $app = new ConsoleApplication();
        $app->register($command);

        $app->run(['bin/console', 'test:dummy', 'first', 'second', 'third']);

        self::assertSame(['first', 'second', 'third'], $command->lastArgs);
    }

    public function testOptionWithEqualsSign(): void
    {
        $command = new DummyCommand();
        $app = new ConsoleApplication();
        $app->register($command);

        $app->run(['bin/console', 'test:dummy', '--module=addressbook']);

        self::assertSame([], $command->lastArgs);
        self::assertSame(['module' => 'addressbook'], $command->lastOptions);
    }

    public function testHelpOptionShowsUsageAndReturnsZero(): void
    {
        $command = new CommandWithUsage();
        $app = new ConsoleApplication();
        $app->register($command);

        $exitCode = $app->run(['bin/console', 'test:usage', '--help']);

        self::assertSame(0, $exitCode);
        self::assertFalse($command->wasExecuted, 'Command should not execute when --help is passed');
    }

    public function testHelpOnCommandWithoutUsageShowsNameAndDescription(): void
    {
        $command = new DummyCommand();
        $app = new ConsoleApplication();
        $app->register($command);

        $exitCode = $app->run(['bin/console', 'test:dummy', '--help']);

        self::assertSame(0, $exitCode);
        self::assertSame([], $command->lastArgs, 'Command should not execute when --help is passed');
    }

    public function testAutoDiscoverFindsCommandsInDirectory(): void
    {
        $app = new ConsoleApplication();
        $commandsDir = dirname(__DIR__, 2) . '/core/Console/Commands';
        $app->discoverCommands($commandsDir);

        // Should find at least the 8 commands we have
        $exitCode = $app->run(['bin/console', 'module:sync', '--help']);
        self::assertSame(0, $exitCode);

        $exitCode = $app->run(['bin/console', 'scheduler:run', '--help']);
        self::assertSame(0, $exitCode);

        $exitCode = $app->run(['bin/console', 'doctor', '--help']);
        self::assertSame(0, $exitCode);
    }

    public function testAutoDiscoverIgnoresNonexistentDirectory(): void
    {
        $app = new ConsoleApplication();
        $app->discoverCommands(sys_get_temp_dir() . '/nonexistent-console-' . uniqid());

        // Should not throw, just have no commands
        $exitCode = $app->run(['bin/console', 'anything']);
        self::assertSame(1, $exitCode);
    }

    public function testProjectRootResolvesCorrectly(): void
    {
        $command = new ProjectRootTestCommand();
        $expectedRoot = realpath(dirname(__DIR__, 2));

        self::assertSame($expectedRoot, realpath($command->getProjectRoot()));
    }
}

final class DummyCommand extends Command
{
    public array $lastArgs = [];
    public array $lastOptions = [];

    public function name(): string
    {
        return 'test:dummy';
    }

    public function description(): string
    {
        return 'A dummy command';
    }

    public function execute(array $args, array $options): int
    {
        $this->lastArgs = $args;
        $this->lastOptions = $options;
        return 0;
    }
}

final class FailingCommand extends Command
{
    public function name(): string
    {
        return 'test:fail';
    }

    public function description(): string
    {
        return 'Always fails';
    }

    public function execute(array $args, array $options): int
    {
        return 42;
    }
}

final class CommandWithUsage extends Command
{
    public bool $wasExecuted = false;

    public function name(): string
    {
        return 'test:usage';
    }

    public function description(): string
    {
        return 'Has custom usage text';
    }

    public function usage(): string
    {
        return "Usage: php bin/console test:usage <arg>\n\nA test command with usage.";
    }

    public function execute(array $args, array $options): int
    {
        $this->wasExecuted = true;
        return 0;
    }
}

final class ProjectRootTestCommand extends Command
{
    public function name(): string
    {
        return 'test:root';
    }

    public function description(): string
    {
        return 'Tests project root';
    }

    public function getProjectRoot(): string
    {
        return $this->projectRoot();
    }

    public function execute(array $args, array $options): int
    {
        return 0;
    }
}
