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

use PHPUnit\Framework\TestCase;
use Domain\PaymentMethod\PaymentMethodRepository;

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

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

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

        $repository = new PaymentMethodRepository($mockDb);
        $this->assertNull($repository->find(10));
    }

    public function testFindNormalizesData(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('get')
            ->with('pp_shop_payment_methods', '*', ['id' => 11])
            ->willReturn([
                'id' => '11',
                'name' => '  Przelew  ',
                'description' => null,
                'status' => '1',
                'apilo_payment_type_id' => '7',
            ]);

        $repository = new PaymentMethodRepository($mockDb);
        $result = $repository->find(11);

        $this->assertIsArray($result);
        $this->assertSame(11, $result['id']);
        $this->assertSame('Przelew', $result['name']);
        $this->assertSame('', $result['description']);
        $this->assertSame(1, $result['status']);
        $this->assertSame(7, $result['apilo_payment_type_id']);
    }

    public function testSaveUpdatesRowAndReturnsId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $updateRow = null;
        $updateWhere = null;

        $mockDb->expects($this->once())
            ->method('update')
            ->willReturnCallback(function ($table, $row, $where) use (&$updateRow, &$updateWhere) {
                $this->assertSame('pp_shop_payment_methods', $table);
                $updateRow = $row;
                $updateWhere = $where;
                return true;
            });

        $repository = new PaymentMethodRepository($mockDb);
        $id = $repository->save(3, [
            'description' => '  test  ',
            'status' => 'on',
            'apilo_payment_type_id' => '22',
        ]);

        $this->assertSame(3, $id);
        $this->assertSame('test', $updateRow['description']);
        $this->assertSame(1, $updateRow['status']);
        $this->assertSame(22, $updateRow['apilo_payment_type_id']);
        $this->assertSame(['id' => 3], $updateWhere);
    }

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

        $mockDb->expects($this->once())
            ->method('update')
            ->willReturnCallback(function ($table, $row) use (&$updateRow) {
                $this->assertSame('pp_shop_payment_methods', $table);
                $updateRow = $row;
                return true;
            });

        $repository = new PaymentMethodRepository($mockDb);
        $repository->save(4, [
            'description' => 'X',
            'status' => 1,
            'apilo_payment_type_id' => 'CASH_ON_DELIVERY',
        ]);

        $this->assertSame('CASH_ON_DELIVERY', $updateRow['apilo_payment_type_id']);
    }

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

        $repository = new PaymentMethodRepository($mockDb);
        $this->assertNull($repository->save(0, ['status' => 1]));
    }

    public function testListForAdminWhitelistsSortAndDirection(): 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' => 'Przelew',
                            'description' => 'Opis',
                            'status' => 1,
                            'apilo_payment_type_id' => 5,
                        ]];
                    }
                };
            });

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

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

        $this->assertMatchesRegularExpression('/ORDER BY\s+spm\.name\s+ASC,\s+spm\.id\s+ASC/i', $dataSql);
        $this->assertStringNotContainsString('DROP TABLE', $dataSql);
        $this->assertStringNotContainsString('DELETE FROM pp_users', $dataSql);
        $this->assertMatchesRegularExpression('/LIMIT\s+100\s+OFFSET\s+0/i', $dataSql);
        $this->assertSame(1, (int)$result['items'][0]['id']);
    }

    public function testAllActiveReturnsNormalizedRows(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('select')
            ->with('pp_shop_payment_methods', '*', [
                'status' => 1,
                'ORDER' => ['id' => 'ASC'],
            ])
            ->willReturn([
                [
                    'id' => '2',
                    'name' => ' PayU ',
                    'description' => '',
                    'status' => '1',
                    'apilo_payment_type_id' => null,
                ],
            ]);

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

        $this->assertCount(1, $rows);
        $this->assertSame(2, $rows[0]['id']);
        $this->assertSame('PayU', $rows[0]['name']);
        $this->assertSame(1, $rows[0]['status']);
        $this->assertNull($rows[0]['apilo_payment_type_id']);
    }

    public function testAllForAdminReturnsRowsIncludingInactive(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('select')
            ->with('pp_shop_payment_methods', '*', [
                'ORDER' => ['name' => 'ASC'],
            ])
            ->willReturn([
                [
                    'id' => '1',
                    'name' => 'Przelew',
                    'description' => '',
                    'status' => '1',
                    'apilo_payment_type_id' => null,
                ],
                [
                    'id' => '2',
                    'name' => 'PayPo',
                    'description' => '',
                    'status' => '0',
                    'apilo_payment_type_id' => null,
                ],
            ]);

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

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

    public function testFindActiveByIdReturnsNullForNotFound(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('get')
            ->with('pp_shop_payment_methods', '*', [
                'AND' => [
                    'id' => 4,
                    'status' => 1,
                ],
            ])
            ->willReturn(null);

        $repository = new PaymentMethodRepository($mockDb);
        $this->assertNull($repository->findActiveById(4));
    }

    public function testFindKeepsNonNumericApiloPaymentTypeId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('get')
            ->with('pp_shop_payment_methods', '*', ['id' => 12])
            ->willReturn([
                'id' => '12',
                'name' => 'PayPo',
                'description' => '',
                'status' => '1',
                'apilo_payment_type_id' => 'PAYPO_DEFERRED',
            ]);

        $repository = new PaymentMethodRepository($mockDb);
        $result = $repository->find(12);

        $this->assertIsArray($result);
        $this->assertSame('PAYPO_DEFERRED', $result['apilo_payment_type_id']);
    }

    public function testIsActiveNormalizesStatusValue(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('get')
            ->with('pp_shop_payment_methods', 'status', ['id' => 5])
            ->willReturn('0');

        $repository = new PaymentMethodRepository($mockDb);
        $this->assertSame(0, $repository->isActive(5));
    }

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

        $repository = new PaymentMethodRepository($mockDb);
        $this->assertNull($repository->getApiloPaymentTypeId(1));
        $this->assertSame(8, $repository->getApiloPaymentTypeId(2));
    }

    public function testGetApiloPaymentTypeIdReturnsStringForNonNumericValue(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('get')
            ->with('pp_shop_payment_methods', 'apilo_payment_type_id', ['id' => 3])
            ->willReturn('BANK_TRANSFER');

        $repository = new PaymentMethodRepository($mockDb);
        $this->assertSame('BANK_TRANSFER', $repository->getApiloPaymentTypeId(3));
    }

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

        $mockDb->expects($this->once())
            ->method('query')
            ->willReturnCallback(function ($sql, $params = []) use (&$capturedParams) {
                $this->assertStringContainsString('pp_shop_transport_payment_methods', $sql);
                $capturedParams = $params;

                return new class {
                    public function fetchAll()
                    {
                        return [[
                            'id' => '9',
                            'name' => 'Karta',
                            'description' => 'Opis',
                            'status' => 1,
                            'apilo_payment_type_id' => '4',
                        ]];
                    }
                };
            });

        $repository = new PaymentMethodRepository($mockDb);
        $rows = $repository->forTransport(12);

        $this->assertSame([':transport_id' => 12], $capturedParams);
        $this->assertCount(1, $rows);
        $this->assertSame(9, $rows[0]['id']);
        $this->assertSame(4, $rows[0]['apilo_payment_type_id']);
    }
}
