<?php

namespace MintyPHP\Tests\Console;

use MintyPHP\Console\Commands\Module\DeactivateCommand;
use MintyPHP\Console\Commands\Module\RuntimeSyncCommand;
use MintyPHP\Console\Runner\Module\ModuleRunnerInterface;
use PHPUnit\Framework\TestCase;

final class ModuleCommandsTest extends TestCase
{
    public function testRuntimeSyncJsonOutputShapeAndExitCode(): void
    {
        $runner = new FakeModuleRunner();
        $runner->runtimeSyncResult = [
            'command' => 'module:sync',
            'status' => 'ok',
            'exit_code' => 0,
            'vendor_warnings_ignored_total' => 2,
            'duration_ms' => 88,
            'steps' => [
                'db-migrate' => ['status' => 'ok', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
                'migrate' => ['status' => 'ok', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
                'permissions-sync' => ['status' => 'ok', 'exit_code' => 0, 'vendor_warnings_ignored' => 1],
                'build' => ['status' => 'ok', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
                'assets-sync' => ['status' => 'ok', 'exit_code' => 0, 'vendor_warnings_ignored' => 1],
            ],
            'message' => 'done',
        ];

        $command = new RuntimeSyncCommand($runner);

        ob_start();
        $exitCode = $command->execute([], ['format' => 'json']);
        $output = (string) ob_get_clean();

        self::assertSame(0, $exitCode);

        /** @var array<string, mixed> $decoded */
        $decoded = json_decode($output, true, 512, JSON_THROW_ON_ERROR);
        self::assertSame('module:sync', $decoded['command']);
        self::assertSame('ok', $decoded['status']);
        self::assertSame(0, $decoded['exit_code']);
        self::assertArrayHasKey('steps', $decoded);
        self::assertArrayHasKey('db-migrate', $decoded['steps']);
        self::assertArrayHasKey('migrate', $decoded['steps']);
        self::assertArrayHasKey('assets-sync', $decoded['steps']);
    }

    public function testRuntimeSyncInvalidFormatReturnsOne(): void
    {
        $command = new RuntimeSyncCommand(new FakeModuleRunner());
        $exitCode = $command->execute([], ['format' => 'xml']);

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

    public function testDeactivateDoesNotMutateGlobalArgvAndPassesOptions(): void
    {
        $runner = new FakeModuleRunner();
        $runner->deactivateResult = [
            'command' => 'module:deactivate',
            'status' => 'ok',
            'exit_code' => 0,
            'vendor_warnings_ignored' => 0,
            'message' => 'ok',
        ];

        $command = new DeactivateCommand($runner);
        $argvBefore = ['bin/console', 'list'];
        $GLOBALS['argv'] = $argvBefore;

        $exitCode = $command->execute(['addressbook'], ['confirm' => true]);

        self::assertSame(0, $exitCode);
        self::assertSame('addressbook', $runner->deactivateModuleId);
        self::assertTrue($runner->deactivateConfirm);
        self::assertFalse($runner->deactivateDryRun);
        self::assertSame($argvBefore, $GLOBALS['argv']);
    }
}

/**
 * @api Test fixture — public properties are mutated by test cases across files.
 */
final class FakeModuleRunner implements ModuleRunnerInterface
{
    /** @var array<string, mixed> */
    public array $runtimeSyncResult = [
        'command' => 'module:sync',
        'status' => 'ok',
        'exit_code' => 0,
        'vendor_warnings_ignored_total' => 0,
        'duration_ms' => 0,
        'steps' => [
            'db-migrate' => ['status' => 'ok', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
            'migrate' => ['status' => 'ok', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
            'permissions-sync' => ['status' => 'ok', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
            'build' => ['status' => 'ok', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
            'assets-sync' => ['status' => 'ok', 'exit_code' => 0, 'vendor_warnings_ignored' => 0],
        ],
        'message' => 'ok',
    ];

    /** @var array<string, mixed> */
    public array $dbMigrateResult = [
        'command' => 'db:migrate',
        'status' => 'ok',
        'exit_code' => 0,
        'vendor_warnings_ignored' => 0,
        'message' => 'db-migrate: all updates already applied.',
    ];

    /** @var array<string, mixed> */
    public array $dbMigrateStatusResult = [
        'command' => 'db:migrate --status',
        'applied' => [],
        'pending' => [],
    ];

    /** @var array<string, mixed> */
    public array $deactivateResult = [
        'command' => 'module:deactivate',
        'status' => 'ok',
        'exit_code' => 0,
        'vendor_warnings_ignored' => 0,
        'message' => 'ok',
    ];

    public string $deactivateModuleId = '';
    public bool $deactivateConfirm = false;
    public bool $deactivateDryRun = false;

    public function dbMigrate(): array
    {
        return $this->dbMigrateResult;
    }

    public function dbMigrateStatus(): array
    {
        return $this->dbMigrateStatusResult;
    }

    public function migrate(): array
    {
        return [
            'command' => 'module:migrate',
            'status' => 'ok',
            'exit_code' => 0,
            'vendor_warnings_ignored' => 0,
            'message' => 'ok',
        ];
    }

    public function permissionsSync(): array
    {
        return [
            'command' => 'module:permissions-sync',
            'status' => 'ok',
            'exit_code' => 0,
            'vendor_warnings_ignored' => 0,
            'created' => 0,
            'updated' => 0,
            'unchanged' => 0,
            'deactivated' => 0,
            'total' => 0,
            'message' => 'ok',
        ];
    }

    public function build(): array
    {
        return [
            'command' => 'module:build',
            'status' => 'ok',
            'exit_code' => 0,
            'vendor_warnings_ignored' => 0,
            'core_entries' => 0,
            'module_entries' => 0,
            'message' => 'ok',
        ];
    }

    public function assetsSync(): array
    {
        return [
            'command' => 'module:assets-sync',
            'status' => 'ok',
            'exit_code' => 0,
            'vendor_warnings_ignored' => 0,
            'mode' => 'symlink',
            'created' => 0,
            'updated' => 0,
            'removed' => 0,
            'unchanged' => 0,
            'message' => 'ok',
        ];
    }

    public function deactivate(string $moduleId, bool $confirm, bool $dryRun): array
    {
        $this->deactivateModuleId = $moduleId;
        $this->deactivateConfirm = $confirm;
        $this->deactivateDryRun = $dryRun;
        return $this->deactivateResult;
    }

    public function runtimeSync(): array
    {
        return $this->runtimeSyncResult;
    }
}
