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

use PHPUnit\Framework\TestCase;
use Domain\Order\OrderRepository;

class OrderRepositoryTest extends TestCase
{
    public function testOrderStatusesReturnsMappedArray(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('select')
            ->willReturnCallback(function ($table, $columns, $where) {
                if ($table === 'pp_shop_statuses') {
                    return [
                        ['id' => 0, 'status' => 'Nowe'],
                        ['id' => 4, 'status' => 'W realizacji'],
                    ];
                }

                return [];
            });

        $repository = new OrderRepository($mockDb);
        $statuses = $repository->orderStatuses();

        $this->assertIsArray($statuses);
        $this->assertSame('Nowe', $statuses[0]);
        $this->assertSame('W realizacji', $statuses[4]);
    }

    public function testOrderStatusDataReturnsBothNamesAndColors(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('select')
            ->willReturnCallback(function ($table, $columns, $where) {
                if ($table === 'pp_shop_statuses') {
                    return [
                        ['id' => 0, 'status' => 'Nowe', 'color' => '#ff0000'],
                        ['id' => 4, 'status' => 'W realizacji', 'color' => '#00ff00'],
                        ['id' => 5, 'status' => 'Wysłane', 'color' => ''],
                    ];
                }
                return [];
            });

        $repository = new OrderRepository($mockDb);
        $data = $repository->orderStatusData();

        $this->assertArrayHasKey('names', $data);
        $this->assertArrayHasKey('colors', $data);
        $this->assertSame('Nowe', $data['names'][0]);
        $this->assertSame('W realizacji', $data['names'][4]);
        $this->assertSame('Wysłane', $data['names'][5]);
        $this->assertSame('#ff0000', $data['colors'][0]);
        $this->assertSame('#00ff00', $data['colors'][4]);
        $this->assertArrayNotHasKey(5, $data['colors']);
    }

    public function testOrderStatusDataFiltersInvalidHexColors(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('select')
            ->willReturn([
                ['id' => 1, 'status' => 'OK', 'color' => '#abc'],
                ['id' => 2, 'status' => 'Bad', 'color' => 'red'],
                ['id' => 3, 'status' => 'XSS', 'color' => '#000" onclick="alert(1)'],
                ['id' => 4, 'status' => 'Valid', 'color' => '#AABBCC'],
            ]);

        $repository = new OrderRepository($mockDb);
        $data = $repository->orderStatusData();

        $this->assertSame('#abc', $data['colors'][1]);
        $this->assertArrayNotHasKey(2, $data['colors']);
        $this->assertArrayNotHasKey(3, $data['colors']);
        $this->assertSame('#AABBCC', $data['colors'][4]);
    }

    public function testOrderStatusDataReturnsEmptyOnDbFailure(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('select')->willReturn(false);

        $repository = new OrderRepository($mockDb);
        $data = $repository->orderStatusData();

        $this->assertSame([], $data['names']);
        $this->assertSame([], $data['colors']);
    }

    public function testNextAndPrevOrderIdReturnNullForInvalidInput(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->never())->method('get');

        $repository = new OrderRepository($mockDb);

        $this->assertNull($repository->nextOrderId(0));
        $this->assertNull($repository->prevOrderId(0));
    }

    public function testListForAdminReturnsItemsAndTotal(): void
    {
        $mockDb = $this->createMock(\medoo::class);

        $resultSetCount = new class {
            public function fetchAll(): array
            {
                return [[3]];
            }
        };

        $resultSetData = new class {
            public function fetchAll(): array
            {
                return [
                    [
                        'id' => 11,
                        'number' => '2026/02/001',
                        'date_order' => '2026-02-15 10:00:00',
                        'client' => 'Jan Kowalski',
                        'order_email' => 'jan@example.com',
                        'address' => 'Testowa 1, 00-000 Warszawa',
                        'status' => 0,
                        'client_phone' => '111222333',
                        'transport' => 'Kurier',
                        'payment_method' => 'Przelew',
                        'summary' => '123.45',
                        'paid' => 1,
                        'total_orders' => 2,
                    ],
                ];
            }
        };

        $callIndex = 0;
        $mockDb->method('query')
            ->willReturnCallback(function () use (&$callIndex, $resultSetCount, $resultSetData) {
                $callIndex++;
                return $callIndex === 1 ? $resultSetCount : $resultSetData;
            });

        $repository = new OrderRepository($mockDb);
        $result = $repository->listForAdmin([], 'date_order', 'DESC', 1, 15);

        $this->assertSame(3, $result['total']);
        $this->assertCount(1, $result['items']);
        $this->assertSame(11, $result['items'][0]['id']);
        $this->assertSame('2026/02/001', $result['items'][0]['number']);
        $this->assertSame(2, $result['items'][0]['total_orders']);
        $this->assertSame(1, $result['items'][0]['paid']);
    }

    // --- Frontend method tests ---

    public function testFindIdByHashReturnsIdWhenFound(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')
            ->with('pp_shop_orders', 'id', ['hash' => 'abc123'])
            ->willReturn('42');

        $repository = new OrderRepository($mockDb);
        $this->assertSame(42, $repository->findIdByHash('abc123'));
    }

    public function testFindIdByHashReturnsNullWhenNotFound(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')->willReturn(null);

        $repository = new OrderRepository($mockDb);
        $this->assertNull($repository->findIdByHash('nonexistent'));
    }

    public function testFindIdByHashReturnsNullForEmptyHash(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->never())->method('get');

        $repository = new OrderRepository($mockDb);
        $this->assertNull($repository->findIdByHash(''));
        $this->assertNull($repository->findIdByHash('   '));
    }

    public function testFindHashByIdReturnsHashWhenFound(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')
            ->with('pp_shop_orders', 'hash', ['id' => 42])
            ->willReturn('abc123hash');

        $repository = new OrderRepository($mockDb);
        $this->assertSame('abc123hash', $repository->findHashById(42));
    }

    public function testFindHashByIdReturnsNullForInvalidId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->never())->method('get');

        $repository = new OrderRepository($mockDb);
        $this->assertNull($repository->findHashById(0));
        $this->assertNull($repository->findHashById(-1));
    }

    public function testOrderDetailsFrontendByIdReturnsArrayWithProducts(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')
            ->willReturn(['id' => 1, 'number' => '2026/02/001', 'coupon_id' => null]);
        $mockDb->method('select')
            ->willReturn([['product_id' => 10, 'name' => 'Test Product']]);

        $repository = new OrderRepository($mockDb);
        $result = $repository->orderDetailsFrontend(1);

        $this->assertIsArray($result);
        $this->assertSame(1, $result['id']);
        $this->assertIsArray($result['products']);
        $this->assertCount(1, $result['products']);
    }

    public function testOrderDetailsFrontendByHashReturnsArrayWithProducts(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')
            ->willReturn(['id' => 5, 'number' => '2026/02/005', 'hash' => 'testhash']);
        $mockDb->method('select')
            ->willReturn([]);

        $repository = new OrderRepository($mockDb);
        $result = $repository->orderDetailsFrontend(null, 'testhash');

        $this->assertIsArray($result);
        $this->assertSame(5, $result['id']);
        $this->assertIsArray($result['products']);
    }

    public function testOrderDetailsFrontendReturnsNullWhenNotFound(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')->willReturn(null);

        $repository = new OrderRepository($mockDb);
        $this->assertNull($repository->orderDetailsFrontend(999));
        $this->assertNull($repository->orderDetailsFrontend(null, 'nonexistent'));
    }

    public function testGenerateOrderNumberFormatsCorrectly(): void
    {
        $mockDb = $this->createMock(\medoo::class);

        $resultSet = new class {
            public function fetchAll(): array
            {
                return [[5]];
            }
        };

        $mockDb->method('query')->willReturn($resultSet);

        $repository = new OrderRepository($mockDb);
        $number = $repository->generateOrderNumber();

        $expectedPrefix = date('Y/m') . '/';
        $this->assertStringStartsWith($expectedPrefix, $number);
        $this->assertSame($expectedPrefix . '006', $number);
    }

    // --- Order product CRUD tests ---

    public function testGetOrderProductReturnsNullForInvalidId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->never())->method('get');

        $repository = new OrderRepository($mockDb);
        $this->assertNull($repository->getOrderProduct(0));
        $this->assertNull($repository->getOrderProduct(-1));
    }

    public function testGetOrderProductReturnsArray(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')
            ->with('pp_shop_order_products', '*', ['id' => 5])
            ->willReturn(['id' => 5, 'order_id' => 1, 'name' => 'Test']);

        $repository = new OrderRepository($mockDb);
        $result = $repository->getOrderProduct(5);

        $this->assertIsArray($result);
        $this->assertSame(5, $result['id']);
    }

    public function testAddOrderProductReturnsNullForInvalidOrderId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->never())->method('insert');

        $repository = new OrderRepository($mockDb);
        $this->assertNull($repository->addOrderProduct(0, ['name' => 'Test']));
    }

    public function testAddOrderProductInsertsAndReturnsId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())->method('insert')
            ->with('pp_shop_order_products', $this->callback(function ($data) {
                return $data['order_id'] === 10
                    && $data['product_id'] === 5
                    && $data['name'] === 'Test Product'
                    && $data['quantity'] === 2;
            }));
        $mockDb->method('id')->willReturn('99');

        $repository = new OrderRepository($mockDb);
        $result = $repository->addOrderProduct(10, [
            'product_id' => 5,
            'name' => 'Test Product',
            'quantity' => 2,
            'price_brutto' => 19.99,
        ]);

        $this->assertSame(99, $result);
    }

    public function testUpdateOrderProductReturnsFalseForInvalidId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->never())->method('update');

        $repository = new OrderRepository($mockDb);
        $this->assertFalse($repository->updateOrderProduct(0, ['quantity' => 3]));
    }

    public function testUpdateOrderProductUpdatesFields(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())->method('update')
            ->with('pp_shop_order_products', $this->callback(function ($data) {
                return $data['quantity'] === 3
                    && $data['price_brutto'] === 25.50;
            }), ['id' => 7]);

        $repository = new OrderRepository($mockDb);
        $result = $repository->updateOrderProduct(7, [
            'quantity' => 3,
            'price_brutto' => 25.50,
        ]);

        $this->assertTrue($result);
    }

    public function testUpdateOrderProductReturnsFalseForEmptyData(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->never())->method('update');

        $repository = new OrderRepository($mockDb);
        $this->assertFalse($repository->updateOrderProduct(7, []));
    }

    public function testDeleteOrderProductReturnsFalseForInvalidId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->never())->method('delete');

        $repository = new OrderRepository($mockDb);
        $this->assertFalse($repository->deleteOrderProduct(0));
    }

    public function testDeleteOrderProductCallsDelete(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())->method('delete')
            ->with('pp_shop_order_products', ['id' => 12]);

        $repository = new OrderRepository($mockDb);
        $this->assertTrue($repository->deleteOrderProduct(12));
    }

    public function testUpdateTransportCostDoesNothingForInvalidId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->never())->method('update');

        $repository = new OrderRepository($mockDb);
        $repository->updateTransportCost(0, 15.0);
    }

    public function testUpdateTransportCostUpdatesOrder(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())->method('update')
            ->with('pp_shop_orders', ['transport_cost' => 12.50], ['id' => 5]);

        $repository = new OrderRepository($mockDb);
        $repository->updateTransportCost(5, 12.50);
    }

    public function testGenerateOrderNumberStartsAt001(): void
    {
        $mockDb = $this->createMock(\medoo::class);

        $resultSet = new class {
            public function fetchAll(): array
            {
                return [[null]];
            }
        };

        $mockDb->method('query')->willReturn($resultSet);

        $repository = new OrderRepository($mockDb);
        $number = $repository->generateOrderNumber();

        $expectedPrefix = date('Y/m') . '/';
        $this->assertSame($expectedPrefix . '001', $number);
    }
}
