<?php

namespace Tests\Unit\Framework;

use ArrayIterator;
use InvalidArgumentException;
use Openguru\OpenCartFramework\Collection\Collection;
use stdClass;
use Tests\Helpers\ExampleClasses\FilterDTO;
use Tests\TestCase;

class GenericCollectionTest extends TestCase
{
    public function testCanAddItemsToCollection(): void
    {
        $collection = new Collection([], FilterDTO::class);

        $filter = new FilterDTO('price', '>', 100);
        $collection->add($filter);

        $this->assertCount(1, $collection);
        $this->assertSame($filter, $collection->all()[0]);
    }

    public function testThrowsExceptionWhenAddingInvalidItem(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('Item must be an instance of');

        $collection = new Collection([], FilterDTO::class);
        $collection->add(new stdClass()); // Invalid item
    }

    public function testCanFindItemByProperty(): void
    {
        $filter1 = new FilterDTO('price', '>', 100);
        $filter2 = new FilterDTO('stock', '<', 50);
        $collection = new Collection([$filter1, $filter2], FilterDTO::class);

        $result = $collection->findByProperty('field', 'stock');

        $this->assertNotNull($result);
        $this->assertSame($filter2, $result);
    }

    public function testReturnsNullWhenItemNotFound(): void
    {
        $filter = new FilterDTO('price', '>', 100);
        $collection = new Collection([$filter], FilterDTO::class);

        $result = $collection->findByProperty('field', 'nonexistent');

        $this->assertNull($result);
    }

    public function testCanCheckIfItemWithSpecificPropertyAndValueExists(): void
    {
        $filter = new FilterDTO('price', '>', 100);
        $collection = new Collection([$filter], FilterDTO::class);

        $exists = $collection->hasValue('field', 'price');
        $notExists = $collection->hasValue('field', 'nonexistent');

        $this->assertTrue($exists);
        $this->assertFalse($notExists);
    }

    public function testImplementsCountable(): void
    {
        $filter1 = new FilterDTO('price', '>', 100);
        $filter2 = new FilterDTO('stock', '<', 50);
        $collection = new Collection([$filter1, $filter2], FilterDTO::class);

        $this->assertCount(2, $collection);
    }

    public function testImplementsIteratorAggregate(): void
    {
        $filter1 = new FilterDTO('price', '>', 100);
        $filter2 = new FilterDTO('stock', '<', 50);
        $collection = new Collection([$filter1, $filter2], FilterDTO::class);

        $iterator = $collection->getIterator();

        $this->assertInstanceOf(ArrayIterator::class, $iterator);
        $this->assertCount(2, $iterator);
    }

    public function testImplementsArrayAccess(): void
    {
        $filter1 = new FilterDTO('price', '>', 100);
        $filter2 = new FilterDTO('stock', '<', 50);
        $collection = new Collection([], FilterDTO::class);

        // Adding items
        $collection[] = $filter1;
        $collection[] = $filter2;

        $this->assertCount(2, $collection);
        $this->assertSame($filter1, $collection[0]);
        $this->assertSame($filter2, $collection[1]);

        // Modifying an item
        $collection[0] = $filter2;
        $this->assertSame($filter2, $collection[0]);

        // Checking existence
        $this->assertTrue(isset($collection[0]));
        $this->assertFalse(isset($collection[5]));

        // Removing an item
        unset($collection[0]);
        $this->assertFalse(isset($collection[0]));
    }

    public function testArrayAccessThrowsExceptionForInvalidType(): void
    {
        $collection = new Collection([], FilterDTO::class);

        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('Item must be an instance of');

        $collection[] = new stdClass(); // Invalid item
    }

    public function testConstructorValidatesItems(): void
    {
        $this->expectException(InvalidArgumentException::class);

        $invalidItems = [new stdClass(), new FilterDTO('price', '>', 100)];
        new Collection($invalidItems, FilterDTO::class);
    }

    public function testCanRetrieveAllItems(): void
    {
        $filter1 = new FilterDTO('price', '>', 100);
        $filter2 = new FilterDTO('stock', '<', 50);
        $collection = new Collection([$filter1, $filter2], FilterDTO::class);

        $allItems = $collection->all();

        $this->assertCount(2, $allItems);
        $this->assertSame($filter1, $allItems[0]);
        $this->assertSame($filter2, $allItems[1]);
    }

    public function testCanGetValueByProperty(): void
    {
        $filter1 = new FilterDTO('price', '>', 100);
        $filter2 = new FilterDTO('category', '=', 'electronics');
        $collection = new Collection([$filter1, $filter2], FilterDTO::class);

        $value = $collection->getValueByProperty('field', 'price');
        $this->assertSame(100, $value);

        $value = $collection->getValueByProperty('field', 'category');
        $this->assertSame('electronics', $value);
    }

    public function testReturnsNullIfPropertyNotFound(): void
    {
        $filter = new FilterDTO('price', '>', 100);
        $collection = new Collection([$filter], FilterDTO::class);

        $value = $collection->getValueByProperty('field', 'nonexistent');
        $this->assertNull($value);
    }

    public function testReturnsNullIfNoItemsInCollection(): void
    {
        $collection = new Collection([], FilterDTO::class);

        $value = $collection->getValueByProperty('field', 'price');
        $this->assertNull($value);
    }
}
