<?php

declare(strict_types=1);

namespace Tests\Codeception\Task\Splitter;

use Codeception\Task\Splitter\TestsSplitterTask;
use Codeception\Task\Splitter\TestsSplitterTrait;
use Consolidation\Log\Logger;
use PHPUnit\Framework\TestCase;
use Robo\Exception\TaskException;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Finder\Finder;
use const Tests\Codeception\Task\TEST_PATH;

/**
 * Class TestsSplitterTaskTest
 *
 * @coversDefaultClass \Codeception\Task\Splitter\TestsSplitterTask
 */
final class TestsSplitterTaskTest extends TestCase
{
    use TestsSplitterTrait;

    public function testRunThrowsExceptionIfCodeceptLoaderIsNotLoaded(): void
    {
        $service = $this->getMockBuilder(TestsSplitterTask::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['doCodeceptLoaderExists'])
            ->getMock();

        $service
            ->method('doCodeceptLoaderExists')
            ->willReturn(false);

        $this->expectException(TaskException::class);
        $this->expectErrorMessage(
            'This task requires Codeception to be loaded. Please require autoload.php of Codeception'
        );
        $service->run();
    }

    public function providerTestLoadTestsWithDifferentPatterns(): array
    {
        return [
            'Cests' => [
                'type' => 'cest',
                'groups' => 2,
                'from' => TEST_PATH . '/fixtures/Cests/',
                'expectedFiles' => 2,
            ],
            'Dependencies' => [
                'type' => 'depends',
                'groups' => 2,
                'from' => TEST_PATH . '/fixtures/DependencyResolutionExampleTests/',
                'expectedFiles' => 2,
            ],
            'Unit Tests' => [
                'type' => 'unit',
                'groups' => 2,
                'from' => TEST_PATH . '/fixtures/Unit/',
                'expectedFiles' => 2,
            ],
            'Circular Dep' => [
                'type' => 'circ_depends',
                'groups' => 1,
                'from' => TEST_PATH . '/fixtures/DependencyResolutionExampleTests2/',
                'expectedFiles' => 0, // Circular dependency
            ],
        ];
    }

    /**
     * @covers ::run
     * @dataProvider providerTestLoadTestsWithDifferentPatterns
     */
    public function testLoadTests(
        string $type,
        int $groups,
        string $from,
        int $expectedFiles
    ): void {
        $task = new TestsSplitterTask($groups);
        $task->setLogger(new Logger(new NullOutput()));
        $task->testsFrom($from);

        $groupTo = TEST_PATH . '/result/group_';
        $task->groupsTo($groupTo);
        $result = $task->run();

        $this->assertNotEmpty($result);

        if ($expectedFiles > 0) {
            $this->assertTrue($result->wasSuccessful());
            $this->assertEquals($expectedFiles, count($result['files']));
        }

        $files = Finder::create()
            ->files()
            ->in(TEST_PATH . '/result/')
            ->name('group_*');

        $this->assertCount($expectedFiles, $files->getIterator());

        for ($i = 1; $i <= $expectedFiles; ++$i) {
            $this->assertFileExists($groupTo . $i);
        }

        // check that the dependencies are ordered correct
        if ('depends' === $type) {
            for ($i = 1; $i <= $expectedFiles; ++$i) {
                $content = explode(PHP_EOL, file_get_contents($groupTo . $i));
                $check = array_flip(
                    array_map(
                        static fn(string $fullPath): string => explode(':', $fullPath)[1],
                        $content
                    )
                );
                if (preg_grep('#Example1Test\.php#', $content)) {
                    $this->assertGreaterThan(
                        $check['testB'],
                        $check['testA'],
                        'The index of testA must be greater than testB to ensure the correct order.'
                    );
                    $this->assertGreaterThan(
                        $check['testA'],
                        $check['testC'],
                        'The index of testC must be greater than testA to ensure the correct order.'
                    );
                }

                if (preg_grep('#Example2Test\.php#', $content)) {
                    $this->assertGreaterThan(
                        $check['testE'],
                        $check['testD'],
                        'The index of testD must be greater than testE to ensure the correct order.'
                    );
                }
            }
        }
    }

    protected function tearDown(): void
    {
        parent::tearDown(); // TODO: Change the autogenerated stub
        $finder = Finder::create()
            ->files()
            ->name('group_*');

        foreach ($finder->in(TEST_PATH . '/result') as $file) {
            unlink($file->getPathname());
        }
    }
}
