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

use PHPUnit\Framework\TestCase;
use Domain\Coupon\CouponRepository;

class CouponRepositoryTest extends TestCase
{
    public function testFindReturnsDefaultCouponForInvalidId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $repository = new CouponRepository($mockDb);

        $result = $repository->find(0);

        $this->assertIsArray($result);
        $this->assertSame(0, (int)$result['id']);
        $this->assertSame(1, (int)$result['status']);
        $this->assertSame(1, (int)$result['one_time']);
        $this->assertSame([], $result['categories']);
    }

    public function testFindNormalizesCouponData(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('get')
            ->with('pp_shop_coupon', '*', ['id' => 15])
            ->willReturn([
                'id' => '15',
                'name' => 'KOD15',
                'status' => '1',
                'send' => 0,
                'used' => '1',
                'type' => '1',
                'amount' => '15.00',
                'one_time' => '0',
                'include_discounted_product' => '1',
                'categories' => '[4,6,6]',
                'used_count' => '3',
            ]);

        $repository = new CouponRepository($mockDb);
        $result = $repository->find(15);

        $this->assertSame(15, (int)$result['id']);
        $this->assertSame(1, (int)$result['status']);
        $this->assertSame(0, (int)$result['send']);
        $this->assertSame(1, (int)$result['used']);
        $this->assertSame(0, (int)$result['one_time']);
        $this->assertSame(1, (int)$result['include_discounted_product']);
        $this->assertSame([4, 6], $result['categories']);
        $this->assertSame(3, (int)$result['used_count']);
    }

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

        $mockDb->expects($this->once())
            ->method('insert')
            ->willReturnCallback(function ($table, $row) use (&$insertRow) {
                $this->assertSame('pp_shop_coupon', $table);
                $insertRow = $row;
            });

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

        $repository = new CouponRepository($mockDb);
        $id = $repository->save([
            'name' => ' KOD25 ',
            'status' => 'on',
            'send' => '1',
            'used' => 0,
            'type' => 1,
            'amount' => '25,50',
            'one_time' => 'on',
            'include_discounted_product' => 'on',
            'categories' => [1, '2', 'abc', 2],
        ]);

        $this->assertSame(321, $id);
        $this->assertIsArray($insertRow);
        $this->assertSame('KOD25', $insertRow['name'] ?? '');
        $this->assertSame(1, (int)($insertRow['status'] ?? 0));
        $this->assertSame(1, (int)($insertRow['send'] ?? 0));
        $this->assertSame(0, (int)($insertRow['used'] ?? 1));
        $this->assertSame('25.50', $insertRow['amount'] ?? null);
        $this->assertSame('[1,2]', $insertRow['categories'] ?? null);
    }

    public function testSaveUpdatesCouponAndReturnsId(): 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_coupon', $table);
                $updateRow = $row;
                $updateWhere = $where;
                return true;
            });

        $mockDb->expects($this->never())->method('insert');
        $mockDb->expects($this->never())->method('id');

        $repository = new CouponRepository($mockDb);
        $id = $repository->save([
            'id' => 77,
            'name' => 'KOD77',
            'status' => '0',
            'send' => 'off',
            'used' => '0',
            'type' => 1,
            'amount' => '',
            'one_time' => '0',
            'include_discounted_product' => 0,
            'categories' => '',
        ]);

        $this->assertSame(77, $id);
        $this->assertIsArray($updateRow);
        $this->assertArrayHasKey('amount', $updateRow);
        $this->assertArrayHasKey('categories', $updateRow);
        $this->assertNull($updateRow['amount']);
        $this->assertNull($updateRow['categories']);
        $this->assertSame(0, (int)($updateRow['status'] ?? 1));
        $this->assertSame(['id' => 77], $updateWhere);
    }

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

        $repository = new CouponRepository($mockDb);
        $this->assertFalse($repository->delete(0));
    }

    public function testDeleteReturnsTrueWhenDatabaseDeleteSucceeds(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('delete')
            ->with('pp_shop_coupon', ['id' => 55])
            ->willReturn(true);

        $repository = new CouponRepository($mockDb);
        $this->assertTrue($repository->delete(55));
    }

    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' => 'KOD',
                            'status' => 1,
                            'used' => 0,
                            'type' => 1,
                            'amount' => '10',
                            'one_time' => 1,
                            'send' => 0,
                            'include_discounted_product' => 0,
                            'categories' => '[3,5]',
                            'date_used' => null,
                            'used_count' => 7,
                        ]];
                    }
                };
            });

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

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

        $this->assertMatchesRegularExpression('/ORDER BY\s+sc\.name\s+ASC,\s+sc\.id\s+DESC/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([3, 5], $result['items'][0]['categories']);
    }

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

        $mockDb->method('select')
            ->willReturnCallback(function ($table, $columns, $where) {
                if ($table === 'pp_shop_categories' && array_key_exists('parent_id', $where)) {
                    if ($where['parent_id'] === null) {
                        return [['id' => 10]];
                    }
                    if ((int)$where['parent_id'] === 10) {
                        return [['id' => 11]];
                    }
                    return [];
                }

                if ($table === 'pp_shop_categories_langs') {
                    if ((int)$where['category_id'] === 10) {
                        return [['lang_id' => 'pl', 'title' => 'Kategoria A']];
                    }
                    if ((int)$where['category_id'] === 11) {
                        return [['lang_id' => 'pl', 'title' => 'Podkategoria A1']];
                    }
                    return [];
                }

                if ($table === 'pp_langs') {
                    return [['id' => 'pl', 'start' => 1, 'o' => 1]];
                }

                return [];
            });

        $mockDb->method('get')
            ->willReturnCallback(function ($table, $columns, $where) {
                if ($table === 'pp_shop_categories') {
                    $id = (int)$where['id'];
                    return ['id' => $id, 'status' => 1];
                }
                return null;
            });

        $repository = new CouponRepository($mockDb);
        $tree = $repository->categoriesTree(null);

        $this->assertCount(1, $tree);
        $this->assertSame(10, (int)$tree[0]['id']);
        $this->assertSame('Kategoria A', $tree[0]['title']);
        $this->assertCount(1, $tree[0]['subcategories']);
        $this->assertSame(11, (int)$tree[0]['subcategories'][0]['id']);
    }
}
