<?php

declare(strict_types=1);

use App\Framework\Console\ExitCode;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Logging\Logger;
use App\Framework\Process\Exceptions\ProcessException;
use App\Framework\Process\SystemProcess;
use App\Framework\Process\ValueObjects\Command;
use App\Framework\Process\ValueObjects\EnvironmentVariables;

beforeEach(function () {
    $this->logger = Mockery::mock(Logger::class);
    $this->logger->shouldReceive('debug')->andReturn(null);

    $this->process = new SystemProcess($this->logger);
});

afterEach(function () {
    Mockery::close();
});

describe('SystemProcess - Basic Execution', function () {
    it('can execute simple command successfully', function () {
        $result = $this->process->run(
            Command::fromArray(['echo', 'hello'])
        );

        expect($result->isSuccess())->toBeTrue()
            ->and($result->exitCode)->toBe(ExitCode::SUCCESS)
            ->and($result->getOutput())->toContain('hello')
            ->and($result->hasOutput())->toBeTrue();
    });

    it('captures stdout correctly', function () {
        $result = $this->process->run(
            Command::fromArray(['echo', 'test output'])
        );

        expect($result->stdout)->toContain('test output')
            ->and($result->getOutput())->toContain('test output');
    });

    it('captures stderr correctly', function () {
        $result = $this->process->run(
            Command::fromString('>&2 echo "error message"')
        );

        expect($result->stderr)->toContain('error message')
            ->and($result->getErrorOutput())->toContain('error message')
            ->and($result->hasErrors())->toBeTrue();
    });

    it('handles failed commands', function () {
        $result = $this->process->run(
            Command::fromString('exit 1')
        );

        expect($result->isFailed())->toBeTrue()
            ->and($result->exitCode)->toBe(ExitCode::GENERAL_ERROR);
    });

    it('tracks execution time', function () {
        $result = $this->process->run(
            Command::fromArray(['sleep', '0.1'])
        );

        expect($result->runtime->toMilliseconds())->toBeGreaterThan(50);
    });
});

describe('SystemProcess - Working Directory', function () {
    it('can execute command in specific directory', function () {
        $tempDir = sys_get_temp_dir();
        $workingDir = FilePath::create($tempDir);

        $result = $this->process->run(
            command: Command::fromString('pwd'),
            workingDirectory: $workingDir
        );

        expect($result->isSuccess())->toBeTrue()
            ->and($result->stdout)->toContain($tempDir);
    });

    it('throws exception for invalid working directory', function () {
        $invalidDir = FilePath::create('/nonexistent/directory/path');

        $this->process->run(
            command: Command::fromArray(['echo', 'test']),
            workingDirectory: $invalidDir
        );
    })->throws(ProcessException::class);
});

describe('SystemProcess - Environment Variables', function () {
    it('can pass environment variables', function () {
        $env = EnvironmentVariables::fromArray([
            'TEST_VAR' => 'test_value',
        ]);

        $result = $this->process->run(
            command: Command::fromString('echo $TEST_VAR'),
            env: $env
        );

        expect($result->stdout)->toContain('test_value');
    });

    it('merges with existing environment', function () {
        $env = EnvironmentVariables::fromArray([
            'CUSTOM_VAR' => 'custom_value',
        ]);

        $result = $this->process->run(
            command: Command::fromString('echo $PATH'),
            env: $env
        );

        // PATH should still be available
        expect($result->stdout)->not->toBeEmpty();
    });
});

describe('SystemProcess - Timeout', function () {
    it('respects timeout for long-running commands', function () {
        $timeout = Duration::fromSeconds(1);

        $result = $this->process->run(
            command: Command::fromArray(['sleep', '0.5']),
            timeout: $timeout
        );

        expect($result->isSuccess())->toBeTrue()
            ->and($result->runtime->toSeconds())->toBeLessThan(1.0);
    });

    it('handles commands that complete within timeout', function () {
        $timeout = Duration::fromSeconds(5);

        $result = $this->process->run(
            command: Command::fromArray(['echo', 'fast']),
            timeout: $timeout
        );

        expect($result->isSuccess())->toBeTrue();
    });
});

describe('SystemProcess - Command Existence', function () {
    it('detects existing commands', function () {
        $exists = $this->process->commandExists('echo');

        expect($exists)->toBeTrue();
    });

    it('detects non-existing commands', function () {
        $exists = $this->process->commandExists('nonexistent_command_xyz');

        expect($exists)->toBeFalse();
    });
});

describe('SystemProcess - Async Execution', function () {
    it('can start process asynchronously', function () {
        $running = $this->process->start(
            Command::fromArray(['sleep', '0.1'])
        );

        expect($running->isRunning())->toBeTrue()
            ->and($running->getPid())->toBeGreaterThan(0);

        $result = $running->wait();

        expect($result->isSuccess())->toBeTrue()
            ->and($running->isRunning())->toBeFalse();
    });

    it('can terminate running process', function () {
        $running = $this->process->start(
            Command::fromArray(['sleep', '10'])
        );

        expect($running->isRunning())->toBeTrue();

        $running->terminate();

        // Give it a moment to terminate
        usleep(100000);

        expect($running->isRunning())->toBeFalse();
    });

    it('can kill running process', function () {
        $running = $this->process->start(
            Command::fromArray(['sleep', '10'])
        );

        expect($running->isRunning())->toBeTrue();

        $running->kill();

        // Give it a moment to be killed
        usleep(100000);

        expect($running->isRunning())->toBeFalse();
    });
});

describe('SystemProcess - ProcessResult', function () {
    it('provides combined output', function () {
        $result = $this->process->run(
            Command::fromString('echo "stdout" && >&2 echo "stderr"')
        );

        $combined = $result->getCombinedOutput();

        expect($combined)->toContain('stdout')
            ->and($combined)->toContain('stderr');
    });

    it('converts result to array', function () {
        $result = $this->process->run(
            Command::fromArray(['echo', 'test'])
        );

        $array = $result->toArray();

        expect($array)->toHaveKeys([
            'command',
            'stdout',
            'stderr',
            'exit_code',
            'exit_description',
            'runtime_ms',
            'success',
        ])
            ->and($array['success'])->toBeTrue()
            ->and($array['exit_code'])->toBe(0);
    });
});
