<?php

declare(strict_types=1);

namespace FeatureTests\Entity;

use apexl\entityCore\collections\EntityCollection;
use apexl\Io\exceptions\LazyLoadException;
use apexl\Io\interfaces\LazyLoadAllowerInterface;
use apexl\Io\TestInfrastructure\FeatureTest;
use Apexl\Tests\Core\Infrastructure\TestEntity;
use Apexl\Tests\Core\Infrastructure\TestRelatedEntity;
use Apexl\Tests\Core\Infrastructure\UsesTestEntities;
use DI\DependencyException;
use DI\NotFoundException;
use PHPUnit\Framework\Attributes\Test;
use function apexl\Io\container;

class LazyLoadTest extends FeatureTest
{
    use UsesTestEntities;

    /**
     * @throws DependencyException
     * @throws NotFoundException
     */
    #[Test]
    function disallowLazyLoadedDataField(): void
    {
        container()->get(LazyLoadAllowerInterface::class)->override(false);

        $entity = TestEntity::factory()->create();

        $found = TestEntity::find($entity->id, ['id']);

        $this->expectException(LazyLoadException::class);

        $found->name;
    }

    /**
     * @throws DependencyException
     * @throws NotFoundException
     */
    #[Test]
    function disallowLazyLoadedVirtualField(): void
    {
        container()->get(LazyLoadAllowerInterface::class)->override(false);

        $entity = TestEntity::factory()->create();

        $found = TestEntity::find($entity->id);

        $this->expectException(LazyLoadException::class);

        $found->name_twice;
    }

    /**
     * @throws DependencyException
     * @throws NotFoundException
     */
    #[Test]
    function disallowLazyLoadedRelationField(): void
    {
        container()->get(LazyLoadAllowerInterface::class)->override(false);

        $entity = TestEntity::factory()->create();

        $found = TestEntity::find($entity->id);

        $this->expectException(LazyLoadException::class);

        $found->belongs_to->name;
    }

    /**
     * @throws DependencyException
     * @throws NotFoundException
     */
    #[Test]
    function lazyLoadedDataField(): void
    {
        container()->get(LazyLoadAllowerInterface::class)->override(true);

        $name = $this->faker->name();
        $entity = TestEntity::factory()->create([
            'name' => $name,
        ]);

        $found = TestEntity::find($entity->id, ['id']);

        $this->assertSame($name, $found->name);
    }

    /**
     * @throws DependencyException
     * @throws NotFoundException
     */
    #[Test]
    function lazyLoadedVirtualField(): void
    {
        container()->get(LazyLoadAllowerInterface::class)->override(true);

        $name = $this->faker->name();
        $entity = TestEntity::factory()->create([
            'name' => $name,
        ]);

        $found = TestEntity::find($entity->id, ['id']);

        $this->assertSame("$name$name", $found->name_twice);
    }

    /**
     * @throws DependencyException
     * @throws NotFoundException
     */
    #[Test]
    function lazyLoadedBelongsToRelationField(): void
    {
        container()->get(LazyLoadAllowerInterface::class)->override(true);

        $entity = TestEntity::factory()->create();
        $relatedName = $this->faker->name();

        $related = TestRelatedEntity::factory()->create([
            'name' => $relatedName,
            'test_entity_id' => $entity->id,
        ]);

        $found = TestEntity::find($entity->id);

        $this->assertSame($related->id, $found->belongs_to->id);
        $this->assertSame($relatedName, $found->belongs_to->name);
        $this->assertSame("$relatedName$relatedName", $found->belongs_to->name_twice);
    }

    /**
     * @throws DependencyException
     * @throws NotFoundException
     */
    #[Test]
    function lazyLoadedHasOneRelationField(): void
    {
        container()->get(LazyLoadAllowerInterface::class)->override(true);

        $relatedName = $this->faker->name();
        $related = TestRelatedEntity::factory()->create([
            'name' => $relatedName,
        ]);

        $entity = TestEntity::factory()->create([
            'related_test_entity_id' => $related->id,
        ]);


        $found = TestEntity::find($entity->id);

        $this->assertSame($related->id, $found->has_one->id);
        $this->assertSame($relatedName, $found->has_one->name);
        $this->assertSame("$relatedName$relatedName", $found->has_one->name_twice);
    }

    /**
     * @throws DependencyException
     * @throws NotFoundException
     */
    #[Test]
    function lazyLoadedBelongsToManyRelationField(): void
    {
        container()->get(LazyLoadAllowerInterface::class)->override(true);

        $entity = TestEntity::factory()->create();

        $relatedName1 = $this->faker->name();
        $relatedName2 = $this->faker->name();
        $relatedName3 = $this->faker->name();
        TestRelatedEntity::factory()->createMany(2);
        $related1 = TestRelatedEntity::factory()->create([
            'name' => $relatedName1,
            'test_entity_id' => $entity->id,
        ]);
        $related2 = TestRelatedEntity::factory()->create([
            'name' => $relatedName2,
            'test_entity_id' => $entity->id,
        ]);
        $related3 = TestRelatedEntity::factory()->create([
            'name' => $relatedName3,
            'test_entity_id' => $entity->id,
        ]);
        TestRelatedEntity::factory()->createMany(2);


        $found = TestEntity::find($entity->id);

        $result = $found->belongs_to_many;
        $this->assertInstanceOf(EntityCollection::class, $result);
        $this->assertCount(3, $result);

        $this->assertSame($related1->id, $result[0]->id);
        $this->assertSame($relatedName1, $result[0]->name);
        $this->assertSame("$relatedName1$relatedName1", $result[0]->name_twice);

        $this->assertSame($related2->id, $result[1]->id);
        $this->assertSame($relatedName2, $result[1]->name);
        $this->assertSame("$relatedName2$relatedName2", $result[1]->name_twice);

        $this->assertSame($related3->id, $result[2]->id);
        $this->assertSame($relatedName3, $result[2]->name);
        $this->assertSame("$relatedName3$relatedName3", $result[2]->name_twice);
    }

    #[Test]
    function lazyLoadedHasManyRelationField(): void
    {
        container()->get(LazyLoadAllowerInterface::class)->override(true);

        $entity = TestEntity::factory()->create();

        $relatedName1 = $this->faker->name();
        $relatedName2 = $this->faker->name();
        $relatedName3 = $this->faker->name();
        TestRelatedEntity::factory()->createMany(2);
        $related1 = TestRelatedEntity::factory()->create([
            'name' => $relatedName1,
        ]);
        $related2 = TestRelatedEntity::factory()->create([
            'name' => $relatedName2,
        ]);
        $related3 = TestRelatedEntity::factory()->create([
            'name' => $relatedName3,
        ]);
        TestRelatedEntity::factory()->createMany(2);


        $found = TestEntity::find($entity->id);

        self::$db->insert($this->testHasManyPivotTableName)
            ->fields([
                'local_id' => $entity->id,
                'remote_id' => $related2->id,
                'weight' => 1,
            ])->execute();

        self::$db->insert($this->testHasManyPivotTableName)
            ->fields([
                'local_id' => $entity->id,
                'remote_id' => $related3->id,
                'weight' => 2,
            ])->execute();

        self::$db->insert($this->testHasManyPivotTableName)
            ->fields([
                'local_id' => $entity->id,
                'remote_id' => $related1->id,
                'weight' => 3,
            ])->execute();

        $result = $found->has_many;
        $this->assertInstanceOf(EntityCollection::class, $result);
        $this->assertCount(3, $result);

        $this->assertSame($related2->id, $result[0]->id);
        $this->assertSame($relatedName2, $result[0]->name);
        $this->assertSame("$relatedName2$relatedName2", $result[0]->name_twice);

        $this->assertSame($related3->id, $result[1]->id);
        $this->assertSame($relatedName3, $result[1]->name);
        $this->assertSame("$relatedName3$relatedName3", $result[1]->name_twice);

        $this->assertSame($related1->id, $result[2]->id);
        $this->assertSame($relatedName1, $result[2]->name);
        $this->assertSame("$relatedName1$relatedName1", $result[2]->name_twice);
    }

    protected function setUp(): void
    {
        parent::setUp();
        $this->createTestEntityTables();
    }


    protected function tearDown(): void
    {
        $this->dropTestEntityTables();
        parent::tearDown();
    }

}