<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\TicketBundle\Tests\Service\Import;

use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use Chill\TicketBundle\Entity\Motive;
use Chill\TicketBundle\Repository\MotiveRepository;
use Chill\TicketBundle\Service\Import\ImportMotivesFromDirectory;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;

/**
 * @internal
 *
 * @coversNothing
 */
class ImportMotivesFromDirectoryTest extends TestCase
{
    use ProphecyTrait;

    public function testImportSmoke(): void
    {
        // 1) Prepare temporary directory with a minimal motives.yaml and a small file
        $tmpBase = sys_get_temp_dir().DIRECTORY_SEPARATOR.'chill_ticket_import_'.bin2hex(random_bytes(6));
        $this->assertTrue(mkdir($tmpBase, 0777, true));

        $filePath = $tmpBase.DIRECTORY_SEPARATOR.'file.txt';
        file_put_contents($filePath, 'hello world');

        $yaml = <<<'YAML'
- label:
    fr: "Test Motive"
  ordering: 12.5
  urgent: true
  supplementary_informations:
    - label:
        fr: "Some note"
  stored_objects:
    - label:
        fr: "Doc title"
      filename: "file.txt"
YAML;
        file_put_contents($tmpBase.DIRECTORY_SEPARATOR.'motives.yaml', $yaml);

        // 2) Create mocks (Prophecy) for dependencies
        $emProphecy = $this->prophesize(EntityManagerInterface::class);
        // persist should be called for StoredObject and Motive at least once
        $emProphecy->persist(Argument::type(StoredObject::class))->shouldBeCalled();
        $capturedMotive = null;
        $emProphecy->persist(Argument::type(Motive::class))
            ->will(function ($args) use (&$capturedMotive) {
                $capturedMotive = $args[0];
            })
            ->shouldBeCalled();
        $emProphecy->flush()->shouldBeCalled();

        $somMock = $this->createMock(StoredObjectManagerInterface::class);
        $somMock
            ->expects($this->once())
            ->method('write')
            ->willReturnCallback(function (StoredObject $so, string $content, ?string $contentType) {
                $this->assertSame('text/plain', $contentType);

                return new StoredObjectVersion($so, 1, [], [], $contentType ?? 'application/octet-stream', 'file.txt');
            });

        $repoProphecy = $this->prophesize(MotiveRepository::class);
        $repoProphecy->findByLabel('Test Motive', 'fr')->willReturn([])->shouldBeCalled();

        // 3) Run the importer
        $importer = new ImportMotivesFromDirectory(
            $emProphecy->reveal(),
            $somMock,
            $repoProphecy->reveal(),
        );

        $importer->import($tmpBase, 'fr');

        // 4) Cleanup
        @unlink($filePath);
        @unlink($tmpBase.DIRECTORY_SEPARATOR.'motives.yaml');
        @rmdir($tmpBase);

        // Verify that ordering has been properly set on the created motive
        $this->assertInstanceOf(Motive::class, $capturedMotive);
        $this->assertSame(12.5, $capturedMotive->getOrdering());
    }

    public function testImportWithParentCreatesAndLinks(): void
    {
        // 1) Prepare temporary directory with a motives.yaml that declares a parent > child label
        $tmpBase = sys_get_temp_dir().DIRECTORY_SEPARATOR.'chill_ticket_import_'.bin2hex(random_bytes(6));
        $this->assertTrue(mkdir($tmpBase, 0777, true));

        $yaml = <<<'YAML'
- label:
    fr: "  Parent  >   Child  "
  ordering: 7
YAML;
        file_put_contents($tmpBase.DIRECTORY_SEPARATOR.'motives.yaml', $yaml);

        // 2) Create repository prophecy: both child and parent are initially missing
        $repoProphecy = $this->prophesize(MotiveRepository::class);
        $repoProphecy->findByLabel('Child', 'fr')->willReturn([])->shouldBeCalled();
        $repoProphecy->findByLabel('Parent', 'fr')->willReturn([])->shouldBeCalled();

        // 3) Capture persisted motives to verify parent/child relation
        $persistedMotives = [];
        $emProphecy = $this->prophesize(EntityManagerInterface::class);
        $emProphecy->persist(Argument::type(Motive::class))
            ->will(function ($args) use (&$persistedMotives) {
                $persistedMotives[] = $args[0];
            })
            ->shouldBeCalled();
        $emProphecy->flush()->shouldBeCalled();

        // StoredObjectManager is unused in this scenario; provide a dummy mock
        $somMock = $this->createMock(StoredObjectManagerInterface::class);

        // 4) Run the importer
        $importer = new ImportMotivesFromDirectory(
            $emProphecy->reveal(),
            $somMock,
            $repoProphecy->reveal(),
        );
        $importer->import($tmpBase, 'fr');

        // 5) Assertions: we should have at least two motives persisted (parent and child)
        $this->assertGreaterThanOrEqual(2, \count($persistedMotives));

        // Identify child and parent
        $child = null;
        $parent = null;
        foreach ($persistedMotives as $m) {
            if (!$m instanceof Motive) {
                continue;
            }
            $label = $m->getLabel();
            $fr = $label['fr'] ?? null;
            if ('Child' === $fr) {
                $child = $m;
            } elseif ('Parent' === $fr) {
                $parent = $m;
            }
        }

        $this->assertInstanceOf(Motive::class, $child, 'Child motive must be created');
        $this->assertInstanceOf(Motive::class, $parent, 'Parent motive must be created');
        $this->assertSame($parent, $child->getParent(), 'Child must reference the created parent');
        $this->assertTrue($parent->isParent(), 'Parent must have at least one child');
        $this->assertSame(7.0, $child->getOrdering(), 'Child ordering must be set from YAML');
        $this->assertSame(0.0, $parent->getOrdering(), 'Parent ordering must remain untouched (default)');

        // 6) Cleanup
        @unlink($tmpBase.DIRECTORY_SEPARATOR.'motives.yaml');
        @rmdir($tmpBase);
    }

    public function testImportReusesParentAcrossItems(): void
    {
        // 1) Prépare un répertoire temporaire avec deux items partageant le même parent
        $tmpBase = sys_get_temp_dir().DIRECTORY_SEPARATOR.'chill_ticket_import_'.bin2hex(random_bytes(6));
        $this->assertTrue(mkdir($tmpBase, 0777, true));

        $yaml = <<<'YAML'
- label:
    fr: "Parent > A"
  ordering: 1
- label:
    fr: "Parent > B"
  ordering: 2
YAML;
        file_put_contents($tmpBase.DIRECTORY_SEPARATOR.'motives.yaml', $yaml);

        // 2) Repository: parent/children absents initialement
        $repoProphecy = $this->prophesize(MotiveRepository::class);
        $repoProphecy->findByLabel('A', 'fr')->willReturn([])->shouldBeCalledTimes(1);
        $repoProphecy->findByLabel('Parent', 'fr')->willReturn([])->shouldBeCalledTimes(1);
        $repoProphecy->findByLabel('B', 'fr')->willReturn([])->shouldBeCalledTimes(1);

        // 3) Capture des persist() pour compter les entités créées
        $persistedMotives = [];
        $emProphecy = $this->prophesize(EntityManagerInterface::class);
        $emProphecy->persist(Argument::type(Motive::class))
            ->will(function ($args) use (&$persistedMotives) {
                $persistedMotives[] = $args[0];
            })
            ->shouldBeCalled();
        $emProphecy->flush()->shouldBeCalled();

        // 4) Exécute l'import
        $somMock = $this->createMock(StoredObjectManagerInterface::class);
        $importer = new ImportMotivesFromDirectory(
            $emProphecy->reveal(),
            $somMock,
            $repoProphecy->reveal(),
        );
        $importer->import($tmpBase, 'fr');

        // 5) Vérifications: un seul parent créé et partagé (déduplication par instance)
        $unique = [];
        foreach ($persistedMotives as $m) {
            if (!$m instanceof Motive) {
                continue;
            }
            $unique[spl_object_hash($m)] = $m;
        }

        $parents = [];
        $children = [];
        foreach ($unique as $m) {
            $label = $m->getLabel();
            $fr = $label['fr'] ?? null;
            if ('Parent' === $fr) {
                $parents[] = $m;
            }
            if ('A' === $fr || 'B' === $fr) {
                $children[] = $m;
            }
        }

        $this->assertCount(1, $parents, 'Le parent doit être créé une seule fois');
        $this->assertCount(2, $children, 'Les deux enfants doivent être créés');
        $this->assertSame($parents[0], $children[0]->getParent());
        $this->assertSame($parents[0], $children[1]->getParent());
        $this->assertTrue($parents[0]->isParent());

        // 6) Nettoyage
        @unlink($tmpBase.DIRECTORY_SEPARATOR.'motives.yaml');
        @rmdir($tmpBase);
    }
}
