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

use PHPUnit\Framework\TestCase;
use Domain\Attribute\AttributeRepository;

class AttributeRepositoryTest extends TestCase
{
    public function testFindAttributeReturnsDefaultAttributeForInvalidId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('max')
            ->with('pp_shop_attributes', 'o')
            ->willReturn(7);

        $repository = new AttributeRepository($mockDb);
        $result = $repository->findAttribute(0);

        $this->assertSame(0, (int)$result['id']);
        $this->assertSame(1, (int)$result['status']);
        $this->assertSame(0, (int)$result['type']);
        $this->assertSame(8, (int)$result['o']);
        $this->assertSame([], $result['languages']);
    }

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

        $mockDb->method('select')
            ->willReturnCallback(function ($table, $columns, $where) {
                if ($table === 'pp_langs') {
                    return [['id' => 'pl', 'start' => 1, 'o' => 1]];
                }

                return [];
            });

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

                if (preg_match('/SELECT\s+COUNT\(0\)\s+FROM\s+pp_shop_attributes\s+AS\s+sa/i', $sql)) {
                    return new class {
                        public function fetchAll(): array
                        {
                            return [[1]];
                        }
                    };
                }

                return new class {
                    public function fetchAll(): array
                    {
                        return [[
                            'id' => '10',
                            'status' => '1',
                            'type' => '2',
                            'o' => '3',
                            'name_default' => '',
                            'name_any' => 'Wzor A',
                            'values_count' => '5',
                            'name_for_sort' => 'Wzor A',
                        ]];
                    }
                };
            });

        $repository = new AttributeRepository($mockDb);
        $result = $repository->listForAdmin([], 'id DESC; DROP TABLE pp_shop_attributes; --', 'DESC; DELETE', 1, 999);

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

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

        $this->assertSame('Wzor A', $result['items'][0]['name']);
        $this->assertSame(5, (int)$result['items'][0]['values_count']);
    }

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

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

                if ($table === 'pp_shop_products_attributes') {
                    return [];
                }

                return [];
            });

        $mockDb->method('count')
            ->willReturnCallback(function ($table, $where) {
                if ($table === 'pp_shop_attributes_values' && (int)($where['AND']['id'] ?? 0) === 11) {
                    return 1;
                }

                return 0;
            });

        $mockDb->method('get')
            ->willReturnCallback(function ($table, $columns, $where) {
                if ($table === 'pp_shop_attributes_values_langs') {
                    return null;
                }

                return null;
            });

        $mockDb->method('insert')
            ->willReturnCallback(function ($table, $row) use (&$insertCalls) {
                $insertCalls[] = ['table' => $table, 'row' => $row];
            });

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

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

        $mockDb->method('delete')
            ->willReturnCallback(function ($table, $where) use (&$deleteCalls) {
                $deleteCalls[] = ['table' => $table, 'where' => $where];
                return true;
            });

        $repository = new AttributeRepository($mockDb);
        $saved = $repository->saveValues(3, [
            'rows' => [
                [
                    'id' => 11,
                    'is_default' => false,
                    'impact_on_the_price' => '',
                    'translations' => [
                        'pl' => ['name' => 'Niebieski', 'value' => 'blue'],
                    ],
                ],
                [
                    'id' => 0,
                    'is_default' => true,
                    'impact_on_the_price' => null,
                    'translations' => [
                        'pl' => ['name' => 'Czerwony', 'value' => 'red'],
                    ],
                ],
            ],
        ]);

        $this->assertTrue($saved);

        $this->assertTrue($this->hasDeleteCall($deleteCalls, 'pp_shop_attributes_values_langs', ['value_id' => 10]));
        $this->assertTrue($this->hasDeleteCall($deleteCalls, 'pp_shop_attributes_values', ['id' => 10]));
        $this->assertTrue($this->hasUpdateCall($updateCalls, 'pp_shop_attributes_values', ['is_default' => 0], ['attribute_id' => 3]));
        $this->assertTrue($this->hasUpdateCall($updateCalls, 'pp_shop_attributes_values', ['is_default' => 1], ['id' => 22]));

        $this->assertTrue($this->hasInsertInto($insertCalls, 'pp_shop_attributes_values'));
        $this->assertTrue($this->hasInsertInto($insertCalls, 'pp_shop_attributes_values_langs'));
    }

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

        $mockDb->method('select')
            ->willReturnCallback(function ($table, $columns, $where) {
                if ($table === 'pp_shop_attributes_values' && $columns === 'id') {
                    return [5];
                }

                if ($table === 'pp_shop_products_attributes') {
                    return [];
                }

                return [];
            });

        $mockDb->method('count')->willReturn(1);

        $mockDb->method('get')
            ->willReturnCallback(function ($table, $columns, $where) {
                if ($table === 'pp_shop_attributes_values_langs' && $columns === 'id') {
                    return 77;
                }

                return null;
            });

        $mockDb->method('update')->willReturn(true);

        $mockDb->method('delete')
            ->willReturnCallback(function ($table, $where) use (&$deleteCalls) {
                $deleteCalls[] = ['table' => $table, 'where' => $where];
                return true;
            });

        $repository = new AttributeRepository($mockDb);
        $saved = $repository->saveValues(9, [
            'rows' => [
                [
                    'id' => 5,
                    'is_default' => true,
                    'impact_on_the_price' => null,
                    'translations' => [
                        'pl' => ['name' => '', 'value' => ''],
                    ],
                ],
            ],
        ]);

        $this->assertTrue($saved);
        $this->assertTrue($this->hasDeleteCall($deleteCalls, 'pp_shop_attributes_values_langs', ['id' => 77]));
    }

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

        $mockDb->method('select')
            ->willReturnCallback(function ($table, $columns, $where) {
                if ($table === 'pp_langs') {
                    return [['id' => 'pl', 'start' => 1, 'o' => 1]];
                }

                return [];
            });

        $mockDb->expects($this->once())
            ->method('get')
            ->with(
                'pp_shop_attributes_values_langs',
                'name',
                ['AND' => ['value_id' => 123, 'lang_id' => 'pl']]
            )
            ->willReturn('Czerwony');

        $repository = new AttributeRepository($mockDb);
        $result = $repository->getAttributeValueById(123);

        $this->assertSame('Czerwony', $result);
    }

    private function hasDeleteCall(array $calls, string $table, array $where): bool
    {
        foreach ($calls as $call) {
            if ($call['table'] === $table && $call['where'] == $where) {
                return true;
            }
        }

        return false;
    }

    private function hasUpdateCall(array $calls, string $table, array $row, array $where): bool
    {
        foreach ($calls as $call) {
            if ($call['table'] === $table && $call['row'] == $row && $call['where'] == $where) {
                return true;
            }
        }

        return false;
    }

    private function hasInsertInto(array $calls, string $table): bool
    {
        foreach ($calls as $call) {
            if ($call['table'] === $table) {
                return true;
            }
        }

        return false;
    }
}
