<?php
namespace Tests\Unit\Domain\Product;

use PHPUnit\Framework\TestCase;
use Domain\Product\ProductRepository;

/**
 * Testy jednostkowe dla ProductRepository
 *
 * Testujemy izolowaną logikę repozytorium z mockami bazy danych
 */
class ProductRepositoryTest extends TestCase
{
    /**
     * Test pobierania ilości produktu - przypadek sukcesu
     */
    public function testGetQuantityReturnsCorrectValue()
    {
        // Arrange (Przygotowanie)
        // Tworzymy mock bazy danych
        $mockDb = $this->createMock(\medoo::class);

        // Konfigurujemy mock - metoda get() zwróci 42
        $mockDb->expects($this->once())
            ->method('get')
            ->with(
                $this->equalTo('pp_shop_products'),
                $this->equalTo('quantity'),
                $this->equalTo(['id' => 123])
            )
            ->willReturn(42);

        $repository = new ProductRepository($mockDb);

        // Act (Działanie)
        $quantity = $repository->getQuantity(123);

        // Assert (Asercja)
        $this->assertEquals(42, $quantity);
        $this->assertIsInt($quantity);
    }

    /**
     * Test pobierania ilości - produkt nie istnieje
     */
    public function testGetQuantityReturnsNullWhenProductNotFound()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);

        // Medoo zwraca false gdy nie znajdzie rekordu
        $mockDb->method('get')->willReturn(false);

        $repository = new ProductRepository($mockDb);

        // Act
        $quantity = $repository->getQuantity(999);

        // Assert
        $this->assertNull($quantity);
    }

    /**
     * Test pobierania produktu po ID
     */
    public function testFindReturnsProductData()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);

        $expectedProduct = [
            'id' => 123,
            'name' => 'Test Product',
            'quantity' => 10,
            'price_brutto' => '99.99'
        ];

        $mockDb->method('get')->willReturn($expectedProduct);

        $repository = new ProductRepository($mockDb);

        // Act
        $product = $repository->find(123);

        // Assert
        $this->assertEquals($expectedProduct, $product);
        $this->assertIsArray($product);
        $this->assertEquals(123, $product['id']);
    }

    /**
     * Test aktualizacji ilości produktu
     */
    public function testUpdateQuantitySuccess()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);

        // Medoo zwraca PDOStatement w przypadku sukcesu
        $mockDb->expects($this->once())
            ->method('update')
            ->with(
                $this->equalTo('pp_shop_products'),
                $this->equalTo(['quantity' => 50]),
                $this->equalTo(['id' => 123])
            )
            ->willReturn($this->createMock(\PDOStatement::class));

        $repository = new ProductRepository($mockDb);

        // Act
        $result = $repository->updateQuantity(123, 50);

        // Assert
        $this->assertTrue($result);
    }

    /**
     * Test pobierania ceny - zwraca cenę regularną gdy brak promocji
     */
    public function testGetPriceReturnsRegularPrice()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')->willReturn([
            'price_brutto' => '99.99',
            'price_brutto_promo' => ''
        ]);

        $repository = new ProductRepository($mockDb);

        // Act
        $price = $repository->getPrice(123);

        // Assert
        $this->assertEquals(99.99, $price);
        $this->assertIsFloat($price);
    }

    /**
     * Test pobierania ceny - zwraca cenę promocyjną gdy jest niższa
     */
    public function testGetPriceReturnsPromoPrice()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')->willReturn([
            'price_brutto' => '99.99',
            'price_brutto_promo' => '79.99'
        ]);

        $repository = new ProductRepository($mockDb);

        // Act
        $price = $repository->getPrice(123);

        // Assert
        $this->assertEquals(79.99, $price);
    }

    /**
     * Test pobierania ceny - zwraca regularną gdy promo jest wyższa
     */
    public function testGetPriceReturnsRegularWhenPromoIsHigher()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')->willReturn([
            'price_brutto' => '49.99',
            'price_brutto_promo' => '79.99'
        ]);

        $repository = new ProductRepository($mockDb);

        // Act
        $price = $repository->getPrice(123);

        // Assert
        $this->assertEquals(49.99, $price);
    }

    /**
     * Test pobierania ceny - zwraca null gdy produkt nie istnieje
     */
    public function testGetPriceReturnsNullWhenNotFound()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')->willReturn(false);

        $repository = new ProductRepository($mockDb);

        // Act
        $price = $repository->getPrice(999);

        // Assert
        $this->assertNull($price);
    }

    /**
     * Test pobierania nazwy produktu - zwraca nazwę
     */
    public function testGetNameReturnsProductName()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('get')
            ->with(
                $this->equalTo('pp_shop_products_langs'),
                $this->equalTo('name'),
                $this->equalTo(['AND' => ['product_id' => 123, 'lang_id' => 'pl']])
            )
            ->willReturn('Testowy produkt');

        $repository = new ProductRepository($mockDb);

        // Act
        $name = $repository->getName(123, 'pl');

        // Assert
        $this->assertEquals('Testowy produkt', $name);
        $this->assertIsString($name);
    }

    /**
     * Test pobierania nazwy - produkt nie istnieje
     */
    public function testGetNameReturnsNullWhenNotFound()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')->willReturn(false);

        $repository = new ProductRepository($mockDb);

        // Act
        $name = $repository->getName(999, 'pl');

        // Assert
        $this->assertNull($name);
    }

    /**
     * Test typu zwracanej wartości
     */
    public function testGetQuantityReturnsInteger()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')->willReturn('25'); // Baza może zwrócić string

        $repository = new ProductRepository($mockDb);

        // Act
        $quantity = $repository->getQuantity(123);

        // Assert
        $this->assertIsInt($quantity); // Sprawdzamy czy konwersja na int zadziałała
        $this->assertEquals(25, $quantity);
    }

    /**
     * Test przywracania produktu z archiwum
     */
    public function testUnarchiveUpdatesProductAndChildren()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);

        $mockDb->expects($this->exactly(2))
            ->method('update')
            ->withConsecutive(
                [
                    $this->equalTo('pp_shop_products'),
                    $this->equalTo(['status' => 1, 'archive' => 0]),
                    $this->equalTo(['id' => 123])
                ],
                [
                    $this->equalTo('pp_shop_products'),
                    $this->equalTo(['status' => 1, 'archive' => 0]),
                    $this->equalTo(['parent_id' => 123])
                ]
            );

        $repository = new ProductRepository($mockDb);

        // Act
        $result = $repository->unarchive(123);

        // Assert
        $this->assertTrue($result);
    }

    /**
     * Test przenoszenia produktu do archiwum
     */
    public function testArchiveUpdatesProductAndChildren()
    {
        // Arrange
        $mockDb = $this->createMock(\medoo::class);

        $mockDb->expects($this->exactly(2))
            ->method('update')
            ->withConsecutive(
                [
                    $this->equalTo('pp_shop_products'),
                    $this->equalTo(['status' => 0, 'archive' => 1]),
                    $this->equalTo(['id' => 456])
                ],
                [
                    $this->equalTo('pp_shop_products'),
                    $this->equalTo(['status' => 0, 'archive' => 1]),
                    $this->equalTo(['parent_id' => 456])
                ]
            );

        $repository = new ProductRepository($mockDb);

        // Act
        $result = $repository->archive(456);

        // Assert
        $this->assertTrue($result);
    }

    /**
     * Test że unarchive zwraca bool
     */
    public function testUnarchiveReturnsBool()
    {
        $mockDb = $this->createMock(\medoo::class);
        $repository = new ProductRepository($mockDb);

        $result = $repository->unarchive(1);
        $this->assertIsBool($result);
    }

    /**
     * Test że archive zwraca bool
     */
    public function testArchiveReturnsBool()
    {
        $mockDb = $this->createMock(\medoo::class);
        $repository = new ProductRepository($mockDb);

        $result = $repository->archive(1);
        $this->assertIsBool($result);
    }

    /**
     * Test allProductsForMassEdit - zwraca mapę id => name
     */
    public function testAllProductsForMassEditReturnsMap()
    {
        $mockDb = $this->createMock(\medoo::class);

        $callIndex = 0;
        $mockDb->method('get')
            ->willReturnCallback(function($table, $column, $where) use (&$callIndex) {
                $callIndex++;
                // 1. domyślny język
                if ($table === 'pp_langs') return 'pl';
                // 2. nazwa produktu 1
                if ($table === 'pp_shop_products_langs' && $where['AND']['product_id'] === 1) return 'Produkt A';
                // 3. nazwa produktu 2
                if ($table === 'pp_shop_products_langs' && $where['AND']['product_id'] === 2) return 'Produkt B';
                return false;
            });

        $mockDb->method('select')
            ->with('pp_shop_products', 'id', ['parent_id' => null])
            ->willReturn([1, 2]);

        $repository = new ProductRepository($mockDb);

        $result = $repository->allProductsForMassEdit();

        $this->assertIsArray($result);
        $this->assertCount(2, $result);
        $this->assertEquals('Produkt A', $result[1]);
        $this->assertEquals('Produkt B', $result[2]);
    }

    /**
     * Test allProductsForMassEdit - pusta lista gdy brak produktów
     */
    public function testAllProductsForMassEditEmptyWhenNoProducts()
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')->willReturn('pl');
        $mockDb->method('select')->willReturn([]);

        $repository = new ProductRepository($mockDb);

        $result = $repository->allProductsForMassEdit();

        $this->assertIsArray($result);
        $this->assertEmpty($result);
    }

    /**
     * Test getProductsByCategory - zwraca listę ID
     */
    public function testGetProductsByCategoryReturnsList()
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('select')
            ->with('pp_shop_products_categories', 'product_id', ['category_id' => 5])
            ->willReturn([10, 20, 30]);

        $repository = new ProductRepository($mockDb);

        $result = $repository->getProductsByCategory(5);

        $this->assertIsArray($result);
        $this->assertCount(3, $result);
        $this->assertEquals([10, 20, 30], $result);
    }

    /**
     * Test getProductsByCategory - pusta lista gdy brak produktów w kategorii
     */
    public function testGetProductsByCategoryReturnsEmptyArray()
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('select')->willReturn([]);

        $repository = new ProductRepository($mockDb);

        $result = $repository->getProductsByCategory(999);

        $this->assertIsArray($result);
        $this->assertEmpty($result);
    }

    /**
     * Test applyDiscountPercent - zwraca null gdy produkt nie istnieje
     */
    public function testApplyDiscountPercentReturnsNullForInvalidProduct()
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')->willReturn(false);

        $repository = new ProductRepository($mockDb);

        $result = $repository->applyDiscountPercent(999, 10.0);

        $this->assertNull($result);
    }

    /**
     * Test applyDiscountPercent - poprawny wynik z rabatem
     */
    public function testApplyDiscountPercentReturnsCorrectPrices()
    {
        $mockDb = $this->createMock(\medoo::class);

        $mockDb->method('get')
            ->willReturn([
                'vat' => 23,
                'price_brutto' => '100.00',
                'price_netto' => '81.30',
            ]);

        $mockDb->method('update')
            ->willReturn($this->createMock(\PDOStatement::class));

        // query zwracający puste kombinacje
        $mockStmt = $this->createMock(\PDOStatement::class);
        $mockStmt->method('fetchAll')->willReturn([]);
        $mockDb->method('query')->willReturn($mockStmt);

        $repository = new ProductRepository($mockDb);

        $result = $repository->applyDiscountPercent(1, 10.0);

        $this->assertIsArray($result);
        $this->assertArrayHasKey('price_brutto', $result);
        $this->assertArrayHasKey('price_brutto_promo', $result);
        $this->assertEquals(100.00, $result['price_brutto']);
        $this->assertEquals(90.00, $result['price_brutto_promo']);
    }

    /**
     * Test applyDiscountPercent - rabat 0% nie tworzy ceny promocyjnej
     */
    public function testApplyDiscountPercentZeroPercentNullsPromo()
    {
        $mockDb = $this->createMock(\medoo::class);

        $mockDb->method('get')
            ->willReturn([
                'vat' => 23,
                'price_brutto' => '100.00',
                'price_netto' => '81.30',
            ]);

        $mockDb->method('update')
            ->willReturn($this->createMock(\PDOStatement::class));

        $mockStmt = $this->createMock(\PDOStatement::class);
        $mockStmt->method('fetchAll')->willReturn([]);
        $mockDb->method('query')->willReturn($mockStmt);

        $repository = new ProductRepository($mockDb);

        $result = $repository->applyDiscountPercent(1, 0.0);

        $this->assertIsArray($result);
        $this->assertNull($result['price_brutto_promo']);
    }
}
