<?php

/*
 * This file is part of Chevere.
 *
 * (c) Rodolfo Berrios <rodolfo@chevere.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Chevere\Tests;

use ArgumentCountError;
use Chevere\Tests\src\TestActionNoParams;
use Chevere\Tests\src\TestActionNoParamsArrayIntResponse;
use Chevere\Tests\src\TestActionObjectConflict;
use Chevere\Tests\src\TestActionParam;
use Chevere\Tests\src\TestActionParamStringRegex;
use Chevere\Workflow\Job;
use InvalidArgumentException;
use OverflowException;
use PHPUnit\Framework\TestCase;
use stdClass;
use function Chevere\Workflow\response;
use function Chevere\Workflow\variable;

final class JobTest extends TestCase
{
    public function testArgumentCountErrorEmpty(): void
    {
        $this->expectException(ArgumentCountError::class);
        $this->expectExceptionMessage(
            '`'
            . TestActionNoParams::class
            . '::__invoke'
            . '` requires 0 argument(s)'
        );
        $action = new TestActionNoParams();
        new Job(
            $action,
            foo: 'extra',
        );
    }

    public function testArgumentCountErrorMissing(): void
    {
        $this->expectException(ArgumentCountError::class);
        $this->expectExceptionMessage(
            <<<PLAIN
            `Chevere\Tests\src\TestActionParam::__invoke` requires 1 argument(s) `[string \$foo]`
            PLAIN
        );
        $action = new TestActionParam();
        new Job(
            $action,
            foo: 'extra',
            nepe: 'extra'
        );
    }

    public function testArgumentCountErrorRequired(): void
    {
        $this->expectException(ArgumentCountError::class);
        $this->expectExceptionMessage(
            <<<PLAIN
            Missing argument(s) [`string \$foo`] for `Chevere\Tests\src\TestActionParam`
            PLAIN
        );
        $action = new TestActionParam();
        new Job($action);
    }

    public function testWithArgumentCountError(): void
    {
        $action = new TestActionNoParams();
        $job = new Job($action);
        $this->expectException(ArgumentCountError::class);
        $job->withArguments(
            foo: 'extra',
        );
    }

    public function testRawArguments(): void
    {
        $action = new TestActionParamStringRegex();
        $success = [
            'foo' => 'foo',
        ];
        $job = new Job($action, ...$success);
        $fileLine = __FILE__ . ':' . (__LINE__ - 1);
        $this->assertSame($fileLine, $job->caller()->__toString());
        $this->assertSame($action, $job->action());
        $this->assertSame($success, $job->arguments());
        $success = [
            'foo' => 'bar',
        ];
        $jobWithArguments = $job->withArguments(...$success);
        $this->assertNotSame($job, $jobWithArguments);
        $this->assertSame($success, $jobWithArguments->arguments());
        $fail = [
            'foo' => '1',
        ];
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage(
            <<<PLAIN
            Argument [foo]: Argument value provided `1` doesn't match the regex `/^foo|bar$/`
            PLAIN
        );
        new Job($action, ...$fail);
    }

    public function testVariableArguments(): void
    {
        $action = new TestActionParamStringRegex();
        $success = [
            'foo' => variable('foo'),
        ];
        $job = new Job($action, ...$success);
        $this->assertSame($action, $job->action());
        $this->assertSame($success, $job->arguments());
    }

    public function testReferenceResponseKey(): void
    {
        $action = new TestActionParamStringRegex();
        $success = [
            'foo' => response('job1', 'output'),
        ];
        $job = (new Job($action, ...$success))->withIsSync(true);
        $this->assertSame($action, $job->action());
        $this->assertSame($success, $job->arguments());
        $this->assertContains('job1', $job->dependencies());
    }

    public function testWithIsSync(): void
    {
        $action = new TestActionNoParams();
        $job = new Job($action);
        $this->assertNotTrue($job->isSync());
        $jobWithSync = $job->withIsSync();
        $this->assertNotSame($job, $jobWithSync);
        $this->assertTrue($jobWithSync->isSync());
        $jobWithSync = $job->withIsSync(true);
        $this->assertNotSame($job, $jobWithSync);
        $this->assertTrue($jobWithSync->isSync());
    }

    public function testWithDependencies(): void
    {
        $action = new TestActionNoParamsArrayIntResponse();
        $job = new Job($action);
        $this->assertSame([], $job->dependencies()->toArray());
        $job = $job->withDepends('foo', 'bar');
        $this->assertSame(['foo', 'bar'], $job->dependencies()->toArray());
        $job = $job->withDepends('foo', 'bar', 'wea');
        $this->assertSame(['foo', 'bar', 'wea'], $job->dependencies()->toArray());
    }

    public function testWithDependenciesOverflow(): void
    {
        $action = new TestActionNoParamsArrayIntResponse();
        $job = new Job($action);
        $this->assertSame([], $job->dependencies()->toArray());
        $this->expectException(OverflowException::class);
        $this->expectExceptionMessage('Job dependencies must be unique');
        $this->expectExceptionMessage('repeated **foo**');
        $job->withDepends('bar', 'foo', 'foo');
    }

    public function testWithWrongDependencies(): void
    {
        $action = new TestActionNoParamsArrayIntResponse();
        $job = new Job($action);
        $this->assertSame([], $job->dependencies()->toArray());
        $this->expectException(InvalidArgumentException::class);
        $job->withDepends('');
    }

    public function testWithRunIfVariable(): void
    {
        $action = new TestActionNoParams();
        $job = new Job($action);
        $variable = variable('wea');
        $job = $job->withRunIf($variable);
        $this->assertSame(
            [$variable],
            $job->runIf()->toArray()
        );
        $this->expectException(OverflowException::class);
        $job->withRunIf($variable, $variable);
    }

    public function testWithRunIfReference(): void
    {
        $action = new TestActionNoParams();
        $job = new Job($action);
        $reference = response('jobN', 'parameter');
        $job = $job->withRunIf($reference);
        $this->assertSame(
            [$reference],
            $job->runIf()->toArray()
        );
        $this->assertTrue($job->dependencies()->contains('jobN'));
        $this->expectException(OverflowException::class);
        $job->withRunIf($reference, $reference);
    }

    public function testWithMissingArgument(): void
    {
        $this->expectException(ArgumentCountError::class);
        $this->expectExceptionMessage(
            'Missing argument(s) [`'
            . stdClass::class
            . ' $path`] for `'
            . TestActionObjectConflict::class
            . '`'
        );
        new Job(
            new TestActionObjectConflict(),
            baz: 'baz',
            bar: variable('foo')
        );
    }
}
