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

use PHPUnit\Framework\TestCase;
use Domain\Order\OrderAdminService;
use Domain\Order\OrderRepository;
use Domain\Product\ProductRepository;
use Domain\Settings\SettingsRepository;
use Domain\Transport\TransportRepository;
use Domain\CronJob\CronJobRepository;
use Domain\CronJob\CronJobType;

class OrderAdminServiceTest extends TestCase
{
    private function createService(
        $orderRepo = null,
        $productRepo = null,
        $settingsRepo = null,
        $transportRepo = null
    ): OrderAdminService {
        if (!$orderRepo) {
            $orderRepo = $this->createMock(OrderRepository::class);
        }

        return new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
    }

    public function testConstructorAcceptsOnlyOrderRepository(): void
    {
        $orderRepo = $this->createMock(OrderRepository::class);
        $service = new OrderAdminService($orderRepo);
        $this->assertInstanceOf(OrderAdminService::class, $service);
    }

    public function testConstructorAcceptsAllDependencies(): void
    {
        $orderRepo = $this->createMock(OrderRepository::class);
        $productRepo = $this->createMock(ProductRepository::class);
        $settingsRepo = $this->createMock(SettingsRepository::class);
        $transportRepo = $this->createMock(TransportRepository::class);

        $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
        $this->assertInstanceOf(OrderAdminService::class, $service);
    }

    public function testSearchProductsReturnsEmptyForEmptyQuery(): void
    {
        $productRepo = $this->createMock(ProductRepository::class);
        $productRepo->expects($this->never())->method('searchProductByNameAjax');

        $service = $this->createService(null, $productRepo);
        $result = $service->searchProducts('', 'pl');

        $this->assertSame([], $result);
    }

    public function testSearchProductsReturnsEmptyWithoutProductRepo(): void
    {
        $service = $this->createService();
        $result = $service->searchProducts('test', 'pl');

        $this->assertSame([], $result);
    }

    public function testSearchProductsReturnsFormattedResults(): void
    {
        $productRepo = $this->createMock(ProductRepository::class);
        $productRepo->method('searchProductByNameAjax')
            ->with('koszulka', 'pl')
            ->willReturn([
                ['product_id' => 10],
                ['product_id' => 20],
            ]);

        $productRepo->method('findCached')
            ->willReturnCallback(function ($id) {
                if ($id === 10) {
                    return [
                        'language' => ['name' => 'Koszulka biała'],
                        'sku' => 'KB-001',
                        'ean' => '',
                        'price_brutto' => 49.99,
                        'price_brutto_promo' => 39.99,
                        'vat' => 23,
                        'quantity' => 15,
                        'parent_id' => 0,
                    ];
                }
                return null; // product 20 not found
            });

        $productRepo->method('getProductImg')
            ->willReturn('/images/products/test.jpg');

        $service = $this->createService(null, $productRepo);
        $results = $service->searchProducts('koszulka', 'pl');

        $this->assertCount(1, $results);
        $this->assertSame(10, $results[0]['product_id']);
        $this->assertSame('Koszulka biała', $results[0]['name']);
        $this->assertSame('KB-001', $results[0]['sku']);
        $this->assertSame(49.99, $results[0]['price_brutto']);
        $this->assertSame(39.99, $results[0]['price_brutto_promo']);
        $this->assertSame(15, $results[0]['quantity']);
    }

    public function testSaveOrderProductsReturnsFalseForInvalidOrderId(): void
    {
        $service = $this->createService();
        $this->assertFalse($service->saveOrderProducts(0, []));
    }

    public function testSaveOrderProductsDeletesRemovedProducts(): void
    {
        $orderRepo = $this->createMock(OrderRepository::class);
        $productRepo = $this->createMock(ProductRepository::class);
        $settingsRepo = $this->createMock(SettingsRepository::class);
        $transportRepo = $this->createMock(TransportRepository::class);

        // Existing products
        $orderRepo->method('orderProducts')
            ->with(1)
            ->willReturn([
                ['id' => 100, 'product_id' => 5, 'quantity' => 2, 'price_brutto' => 10, 'price_brutto_promo' => 0],
                ['id' => 101, 'product_id' => 6, 'quantity' => 1, 'price_brutto' => 20, 'price_brutto_promo' => 0],
            ]);

        // Product 100 is submitted with delete flag
        // Product 101 is not submitted at all (also deleted)
        $orderRepo->expects($this->exactly(2))->method('deleteOrderProduct');
        $orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]);
        $transportRepo->method('findActiveById')->willReturn(null);

        // Stock should be returned for both deleted products
        $productRepo->method('getQuantity')->willReturn(10);
        $productRepo->expects($this->exactly(2))->method('updateQuantity');

        $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
        $result = $service->saveOrderProducts(1, [
            ['order_product_id' => 100, 'delete' => '1', 'quantity' => 2],
        ]);

        $this->assertTrue($result);
    }

    public function testSaveOrderProductsUpdatesQuantityAndAdjustsStock(): void
    {
        $orderRepo = $this->createMock(OrderRepository::class);
        $productRepo = $this->createMock(ProductRepository::class);
        $settingsRepo = $this->createMock(SettingsRepository::class);
        $transportRepo = $this->createMock(TransportRepository::class);

        // Existing: qty=3
        $orderRepo->method('orderProducts')
            ->willReturn([
                ['id' => 100, 'product_id' => 5, 'quantity' => 3, 'price_brutto' => 10, 'price_brutto_promo' => 0],
            ]);

        // Submit: qty=5 (increased by 2 → stock decreases by 2)
        $orderRepo->expects($this->once())->method('updateOrderProduct')
            ->with(100, $this->callback(function ($data) {
                return $data['quantity'] === 5;
            }));

        $orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]);
        $transportRepo->method('findActiveById')->willReturn(null);

        $productRepo->method('getQuantity')->with(5)->willReturn(20);
        $productRepo->expects($this->once())->method('updateQuantity')
            ->with(5, 18); // 20 + (3 - 5) = 18

        $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
        $service->saveOrderProducts(1, [
            ['order_product_id' => 100, 'product_id' => 5, 'quantity' => 5, 'price_brutto' => 10, 'price_brutto_promo' => 0],
        ]);
    }

    public function testSaveOrderProductsAddsNewProductAndDecreasesStock(): void
    {
        $orderRepo = $this->createMock(OrderRepository::class);
        $productRepo = $this->createMock(ProductRepository::class);
        $settingsRepo = $this->createMock(SettingsRepository::class);
        $transportRepo = $this->createMock(TransportRepository::class);

        $orderRepo->method('orderProducts')->willReturn([]);

        $orderRepo->expects($this->once())->method('addOrderProduct')
            ->with(1, $this->callback(function ($data) {
                return $data['product_id'] === 10
                    && $data['name'] === 'New Product'
                    && $data['quantity'] === 2;
            }));

        $orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]);
        $transportRepo->method('findActiveById')->willReturn(null);

        $productRepo->method('getQuantity')->with(10)->willReturn(15);
        $productRepo->expects($this->once())->method('updateQuantity')
            ->with(10, 13); // 15 - 2

        $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
        $service->saveOrderProducts(1, [
            [
                'order_product_id' => 0,
                'product_id' => 10,
                'parent_product_id' => 10,
                'name' => 'New Product',
                'vat' => 23,
                'price_brutto' => 50,
                'price_brutto_promo' => 0,
                'quantity' => 2,
            ],
        ]);
    }

    public function testGetFreeDeliveryThresholdReturnsZeroWithoutSettingsRepo(): void
    {
        $service = $this->createService();
        $this->assertSame(0.0, $service->getFreeDeliveryThreshold());
    }

    public function testGetFreeDeliveryThresholdReturnsValue(): void
    {
        $settingsRepo = $this->createMock(SettingsRepository::class);
        $settingsRepo->method('getSingleValue')
            ->with('free_delivery')
            ->willReturn('150.00');

        $service = $this->createService(null, null, $settingsRepo);
        $this->assertSame(150.0, $service->getFreeDeliveryThreshold());
    }

    // =========================================================================
    // queueApiloSync — DB-based via CronJobRepository
    // =========================================================================

    public function testConstructorAcceptsCronJobRepo(): void
    {
        $orderRepo = $this->createMock(OrderRepository::class);
        $cronJobRepo = $this->createMock(CronJobRepository::class);
        $service = new OrderAdminService($orderRepo, null, null, null, $cronJobRepo);
        $this->assertInstanceOf(OrderAdminService::class, $service);
    }
}
