<?php

declare(strict_types=1);

namespace NoahVet\Reef\Test\A_Unit\RabbitMQ\Task\Executor;

use NoahVet\Reef\Factory\Process\ProcessFactoryInterface;
use NoahVet\Reef\RabbitMQ\Task\ConsoleCommandTask;
use NoahVet\Reef\RabbitMQ\Task\Executor\NewProcessConsoleTaskCommandExecutor;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Process\Process;

class NewProcessConsoleTaskCommandExecutorTest extends TestCase
{
    private NewProcessConsoleTaskCommandExecutor $executor;

    /**
     * @var ProcessFactoryInterface&MockObject
     */
    private ProcessFactoryInterface $processFactory;

    protected function setUp(): void
    {
        $this->processFactory = $this->createMock(ProcessFactoryInterface::class);

        $this->executor = new NewProcessConsoleTaskCommandExecutor($this->processFactory);
    }

    public function testExecuteSuccessfulProcess(): void
    {
        $mockCommand = $this->createMock(Command::class);
        $mockCommand->method('getName')->willReturn('app:sample-command');
        $inputDefinition = $this->createMock(\Symfony\Component\Console\Input\InputDefinition::class);
        $inputDefinition->method('getArguments')->willReturn([
            new InputArgument('arg1', InputArgument::REQUIRED, 'Argument 1'),
        ]);
        $mockCommand->method('getDefinition')->willReturn($inputDefinition);

        $task = new ConsoleCommandTask($mockCommand, ['arg1' => 'value1', '--flag' => 'test']);

        $output = new BufferedOutput();

        $processMock = $this->createMock(Process::class);
        $processMock->expects($this->once())->method('enableOutput');
        $processMock->expects($this->once())->method('start');
        $processMock->expects($this->once())->method('wait')
            ->willReturnCallback(function (callable $callback) {
                $callback(Process::OUT, "Process output line 1\n");
                $callback(Process::OUT, "Process output line 2\n");

                return 0; // Process exit code
            })
        ;

        $this->processFactory
            ->expects($this->once())
            ->method('createProcess')
            ->with(['/usr/local/bin/php', 'bin/console', 'app:sample-command', 'value1', '--flag=test'])
            ->willReturn($processMock)
        ;

        $exitCode = $this->executor->execute($task, $output);

        $this->assertSame(0, $exitCode);
        $outputContent = $output->fetch();
        $this->assertStringContainsString('Process output line 1', $outputContent);
        $this->assertStringContainsString('Process output line 2', $outputContent);
    }

    public function testExecuteWithInvalidCommandName(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('Command name cannot be null.');

        $mockCommand = $this->createMock(Command::class);
        $mockCommand->method('getName')->willReturn(null);

        $task = new ConsoleCommandTask($mockCommand, ['arg1' => 'value1']);

        $this->executor->execute($task);
    }

    public function testExecuteWithoutOutputInterface(): void
    {
        $mockCommand = $this->createMock(Command::class);
        $mockCommand->method('getName')->willReturn('app:dummy-command');

        $task = new ConsoleCommandTask($mockCommand, []);

        $processMock = $this->createMock(Process::class);
        $processMock->expects($this->once())->method('enableOutput');
        $processMock->expects($this->once())->method('start');
        $processMock->expects($this->once())->method('wait')
            ->willReturnCallback(function (callable $callback) {
                $callback(Process::ERR, "Test output\n");

                return 1;
            })
        ;
        $this->processFactory
            ->method('createProcess')
            ->willReturn($processMock)
        ;

        $exitCode = $this->executor->execute($task);

        $this->assertSame(1, $exitCode);
    }
}
