<?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\DocStoreBundle\Tests\Service;

use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Entity\StoredObjectPointInTime;
use Chill\DocStoreBundle\Entity\StoredObjectPointInTimeReasonEnum;
use Chill\DocStoreBundle\Service\StoredObjectDuplicate;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\NullLogger;

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

    public function testDuplicateHappyScenario(): void
    {
        $storedObject = new StoredObject();
        // we create multiple version, we want the last to be duplicated
        $storedObject->registerVersion(type: 'application/test');
        $version = $storedObject->registerVersion(type: $type = 'application/test');

        $manager = $this->createMock(StoredObjectManagerInterface::class);
        $manager->method('read')->with($version)->willReturn('1234');
        $manager
            ->expects($this->once())
            ->method('write')
            ->with($this->isInstanceOf(StoredObject::class), '1234', 'application/test')
            ->willReturnCallback(fn (StoredObject $so, $content, $type) => $so->registerVersion(type: $type));

        $storedObjectDuplicate = new StoredObjectDuplicate($manager, new NullLogger());

        $actual = $storedObjectDuplicate->duplicate($storedObject);

        self::assertNotNull($actual->getCurrentVersion());
        self::assertNotNull($actual->getCurrentVersion()->getCreatedFrom());
        self::assertSame($version, $actual->getCurrentVersion()->getCreatedFrom());
    }

    public function testDuplicateWithKeptVersion(): void
    {
        $storedObject = new StoredObject();
        // we create two versions for stored object
        // the first one is "kept before conversion", and that one should
        // be duplicated, not the second one
        $version1 = $storedObject->registerVersion(type: $type = 'application/test');
        new StoredObjectPointInTime($version1, StoredObjectPointInTimeReasonEnum::KEEP_BEFORE_CONVERSION);
        $version2 = $storedObject->registerVersion(type: $type = 'application/test');

        $manager = $this->prophesize(StoredObjectManagerInterface::class);

        // we create both possibilities for the method "read"
        $manager->read($version1)->willReturn('1234');
        $manager->read($version2)->willReturn('4567');

        // we create the write method, and check that it is called with the content from version1, not version2
        $manager->write(Argument::type(StoredObject::class), '1234', 'application/test')
            ->shouldBeCalled()
            ->will(function ($args) {
                /** @var StoredObject $storedObject */
                $storedObject = $args[0]; // args are ordered by key, so the first one is the stored object...
                $type = $args[2]; // and the last one is the string $type

                return $storedObject->registerVersion(type: $type);
            });

        // we create the service which will duplicate things
        $storedObjectDuplicate = new StoredObjectDuplicate($manager->reveal(), new NullLogger());

        $actual = $storedObjectDuplicate->duplicate($storedObject);

        self::assertNotNull($actual->getCurrentVersion());
        self::assertNotNull($actual->getCurrentVersion()->getCreatedFrom());
        self::assertSame($version1, $actual->getCurrentVersion()->getCreatedFrom());
    }

    public function testDuplicateWithKeptVersionButWeWantToDuplicateTheLastOne(): void
    {
        $storedObject = new StoredObject();
        // we create two versions for stored object
        // the first one is "kept before conversion", and that one should
        // be duplicated, not the second one
        $version1 = $storedObject->registerVersion(type: $type = 'application/test');
        new StoredObjectPointInTime($version1, StoredObjectPointInTimeReasonEnum::KEEP_BEFORE_CONVERSION);
        $version2 = $storedObject->registerVersion(type: $type = 'application/test');

        $manager = $this->prophesize(StoredObjectManagerInterface::class);

        // we create both possibilities for the method "read"
        $manager->read($version1)->willReturn('1234');
        $manager->read($version2)->willReturn('4567');

        // we create the write method, and check that it is called with the content from version1, not version2
        $manager->write(Argument::type(StoredObject::class), '4567', 'application/test')
            ->shouldBeCalled()
            ->will(function ($args) {
                /** @var StoredObject $storedObject */
                $storedObject = $args[0]; // args are ordered by key, so the first one is the stored object...
                $type = $args[2]; // and the last one is the string $type

                return $storedObject->registerVersion(type: $type);
            });

        // we create the service which will duplicate things
        $storedObjectDuplicate = new StoredObjectDuplicate($manager->reveal(), new NullLogger());

        $actual = $storedObjectDuplicate->duplicate($storedObject, false);

        self::assertNotNull($actual->getCurrentVersion());
        self::assertNotNull($actual->getCurrentVersion()->getCreatedFrom());
        self::assertSame($version2, $actual->getCurrentVersion()->getCreatedFrom());
    }
}
