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

use PHPUnit\Framework\TestCase;
use Domain\Transport\TransportRepository;

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

        $repository = new TransportRepository($mockDb);
        $this->assertNull($repository->find(0));
        $this->assertNull($repository->find(-1));
    }

    public function testFindReturnsNullWhenNotFound(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('get')
            ->with('pp_shop_transports', '*', ['id' => 5])
            ->willReturn(null);

        $repository = new TransportRepository($mockDb);
        $this->assertNull($repository->find(5));
    }

    public function testFindNormalizesDataAndIncludesPaymentMethods(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('get')
            ->with('pp_shop_transports', '*', ['id' => 3])
            ->willReturn([
                'id' => '3',
                'name' => 'Kurier DPD',
                'name_visible' => 'DPD',
                'description' => 'Opis',
                'status' => '1',
                'cost' => '15.99',
                'max_wp' => '30',
                'default' => '0',
                'delivery_free' => '1',
                'apilo_carrier_account_id' => '42',
                'o' => '2',
            ]);

        $mockDb->expects($this->once())
            ->method('select')
            ->with('pp_shop_transport_payment_methods', 'id_payment_method', ['id_transport' => 3])
            ->willReturn([1, 3, 5]);

        $repository = new TransportRepository($mockDb);
        $result = $repository->find(3);

        $this->assertIsArray($result);
        $this->assertSame(3, $result['id']);
        $this->assertSame('Kurier DPD', $result['name']);
        $this->assertSame(1, $result['status']);
        $this->assertSame(15.99, $result['cost']);
        $this->assertSame(30, $result['max_wp']);
        $this->assertSame(0, $result['default']);
        $this->assertSame(1, $result['delivery_free']);
        $this->assertSame(42, $result['apilo_carrier_account_id']);
        $this->assertSame(2, $result['o']);
        $this->assertSame([1, 3, 5], $result['payment_methods']);
    }

    public function testFindHandlesNullMaxWpAndApiloId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->method('get')
            ->willReturn([
                'id' => '1',
                'name' => 'Test',
                'status' => '0',
                'cost' => '0',
                'max_wp' => null,
                'default' => '0',
                'delivery_free' => '0',
                'apilo_carrier_account_id' => null,
                'o' => '0',
            ]);
        $mockDb->method('select')->willReturn([]);

        $repository = new TransportRepository($mockDb);
        $result = $repository->find(1);

        $this->assertNull($result['max_wp']);
        $this->assertNull($result['apilo_carrier_account_id']);
        $this->assertSame([], $result['payment_methods']);
    }

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

        $mockDb->expects($this->once())
            ->method('insert')
            ->with('pp_shop_transports', $this->callback(function ($data) {
                return $data['name'] === 'Nowy transport'
                    && $data['status'] === 1
                    && $data['cost'] === 10.5;
            }));

        $mockDb->expects($this->once())
            ->method('id')
            ->willReturn('7');

        $repository = new TransportRepository($mockDb);
        $id = $repository->save([
            'id' => 0,
            'name' => 'Nowy transport',
            'status' => 'on',
            'cost' => '10.5',
            'default' => '0',
            'delivery_free' => 0,
        ]);

        $this->assertSame(7, $id);
    }

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

        $mockDb->expects($this->once())
            ->method('update')
            ->with('pp_shop_transports', $this->isType('array'), ['id' => 4]);

        $mockDb->expects($this->once())
            ->method('delete')
            ->with('pp_shop_transport_payment_methods', ['id_transport' => 4]);

        $repository = new TransportRepository($mockDb);
        $id = $repository->save([
            'id' => 4,
            'name' => 'Update',
            'status' => 1,
            'cost' => 5.0,
            'default' => 0,
            'delivery_free' => 'on',
        ]);

        $this->assertSame(4, $id);
    }

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

        $repository = new TransportRepository($mockDb);
        $this->assertNull($repository->save([
            'id' => 0,
            'name' => 'Fail',
        ]));
    }

    public function testSaveResetsDefaultWhenSettingNew(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $updateCalls = [];

        $mockDb->method('update')
            ->willReturnCallback(function ($table, $data, $where = null) use (&$updateCalls) {
                $updateCalls[] = ['table' => $table, 'data' => $data, 'where' => $where];
            });

        $mockDb->method('delete');

        $repository = new TransportRepository($mockDb);
        $repository->save([
            'id' => 2,
            'name' => 'X',
            'default' => 'on',
            'status' => 0,
            'delivery_free' => 0,
        ]);

        $this->assertCount(2, $updateCalls);
        $this->assertSame(['default' => 0], $updateCalls[0]['data']);
        $this->assertNull($updateCalls[0]['where']);
    }

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

        $mockDb->method('update')
            ->willReturnCallback(function ($table, $data) use (&$savedData) {
                $savedData = $data;
            });
        $mockDb->method('delete');

        $repository = new TransportRepository($mockDb);
        $repository->save([
            'id' => 1,
            'name' => 'T',
            'status' => 'on',
            'default' => 'true',
            'delivery_free' => '1',
            'cost' => 0,
        ]);

        $this->assertSame(1, $savedData['status']);
        $this->assertSame(1, $savedData['default']);
        $this->assertSame(1, $savedData['delivery_free']);
    }

    public function testListForAdminWhitelistsSortColumn(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $queries = [];

        $mockDb->method('query')
            ->willReturnCallback(function ($sql, $params = []) use (&$queries) {
                $queries[] = ['sql' => $sql, 'params' => $params];

                if (strpos($sql, 'COUNT(0)') !== false) {
                    return new class {
                        public function fetchAll() { return [[1]]; }
                    };
                }

                return new class {
                    public function fetchAll() {
                        return [[
                            'id' => 1, 'name' => 'DPD', 'name_visible' => '',
                            'description' => '', 'status' => 1, 'cost' => 10.0,
                            'max_wp' => null, 'default' => 0,
                            'apilo_carrier_account_id' => null, 'delivery_free' => 0, 'o' => 1,
                        ]];
                    }
                };
            });

        $repository = new TransportRepository($mockDb);
        $result = $repository->listForAdmin(
            [],
            'name DESC; DROP TABLE pp_shop_transports; --',
            'DESC; DELETE FROM pp_users; --',
            1,
            999
        );

        $this->assertCount(2, $queries);
        $dataSql = $queries[1]['sql'];

        $this->assertMatchesRegularExpression('/ORDER BY\s+st\.name\s+ASC,\s+st\.id\s+ASC/i', $dataSql);
        $this->assertStringNotContainsString('DROP TABLE', $dataSql);
        $this->assertMatchesRegularExpression('/LIMIT\s+100\s+OFFSET\s+0/i', $dataSql);
    }

    public function testAllActiveReturnsNormalizedRows(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('select')
            ->willReturn([
                [
                    'id' => '2', 'name' => 'InPost', 'status' => '1',
                    'cost' => '9.99', 'max_wp' => '25', 'default' => '1',
                    'delivery_free' => '0', 'apilo_carrier_account_id' => null, 'o' => '1',
                ],
            ]);

        $repository = new TransportRepository($mockDb);
        $rows = $repository->allActive();

        $this->assertCount(1, $rows);
        $this->assertSame(2, $rows[0]['id']);
        $this->assertSame(9.99, $rows[0]['cost']);
        $this->assertSame(1, $rows[0]['status']);
        $this->assertSame(1, $rows[0]['default']);
    }

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

        $repository = new TransportRepository($mockDb);
        $this->assertNull($repository->getApiloCarrierAccountId(0));
    }

    public function testGetApiloCarrierAccountIdReturnsIntOrNull(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->exactly(2))
            ->method('get')
            ->willReturnOnConsecutiveCalls(null, '55');

        $repository = new TransportRepository($mockDb);
        $this->assertNull($repository->getApiloCarrierAccountId(1));
        $this->assertSame(55, $repository->getApiloCarrierAccountId(2));
    }

    public function testGetTransportCostReturnsFloatOrNull(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->exactly(2))
            ->method('get')
            ->willReturnOnConsecutiveCalls(null, '12.50');

        $repository = new TransportRepository($mockDb);
        $this->assertNull($repository->getTransportCost(99));
        $this->assertSame(12.5, $repository->getTransportCost(1));
    }

    public function testAllForAdminReturnsAllTransports(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('select')
            ->willReturn([
                ['id' => '1', 'name' => 'A', 'status' => '1', 'cost' => '5', 'max_wp' => null, 'default' => '0', 'delivery_free' => '0', 'apilo_carrier_account_id' => null, 'o' => '1'],
                ['id' => '2', 'name' => 'B', 'status' => '0', 'cost' => '10', 'max_wp' => '50', 'default' => '1', 'delivery_free' => '1', 'apilo_carrier_account_id' => '3', 'o' => '2'],
            ]);

        $repository = new TransportRepository($mockDb);
        $rows = $repository->allForAdmin();

        $this->assertCount(2, $rows);
        $this->assertSame(0, $rows[1]['status']);
        $this->assertSame(1, $rows[1]['default']);
        $this->assertSame(50, $rows[1]['max_wp']);
    }
}
