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

use PHPUnit\Framework\TestCase;
use Domain\Producer\ProducerRepository;

class ProducerRepositoryTest extends TestCase
{
    public function testFindReturnsDefaultProducerForInvalidId(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $repository = new ProducerRepository($mockDb);

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

        $this->assertIsArray($result);
        $this->assertSame(0, $result['id']);
        $this->assertSame('', $result['name']);
        $this->assertSame(1, $result['status']);
        $this->assertNull($result['img']);
        $this->assertSame([], $result['languages']);
    }

    public function testFindNormalizesProducerData(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('get')
            ->with('pp_shop_producer', '*', ['id' => 5])
            ->willReturn([
                'id' => '5',
                'name' => 'Apple',
                'status' => '1',
                'img' => '/logo.png',
            ]);

        $mockDb->expects($this->once())
            ->method('select')
            ->with('pp_shop_producer_lang', '*', ['producer_id' => 5])
            ->willReturn([
                [
                    'lang_id' => 'pl',
                    'description' => 'Opis PL',
                    'data' => 'Dane PL',
                    'meta_title' => 'Meta PL',
                ],
                [
                    'lang_id' => 'en',
                    'description' => 'Desc EN',
                    'data' => null,
                    'meta_title' => null,
                ],
            ]);

        $repository = new ProducerRepository($mockDb);
        $result = $repository->find(5);

        $this->assertSame(5, $result['id']);
        $this->assertSame('Apple', $result['name']);
        $this->assertSame(1, $result['status']);
        $this->assertSame('/logo.png', $result['img']);
        $this->assertArrayHasKey('pl', $result['languages']);
        $this->assertArrayHasKey('en', $result['languages']);
        $this->assertSame('Opis PL', $result['languages']['pl']['description']);
        $this->assertSame('Meta PL', $result['languages']['pl']['meta_title']);
        $this->assertNull($result['languages']['en']['meta_title']);
    }

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

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

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

        $langs = [['id' => 'pl'], ['id' => 'en']];

        $repository = new ProducerRepository($mockDb);
        $id = $repository->save(
            0, 'Samsung', 1, '/samsung.png',
            ['pl' => 'Opis PL', 'en' => 'Desc EN'],
            ['pl' => 'Dane', 'en' => null],
            ['pl' => 'Meta PL', 'en' => 'Meta EN'],
            $langs
        );

        $this->assertSame(42, $id);

        // 1st insert: pp_shop_producer
        $this->assertSame('pp_shop_producer', $insertCalls[0]['table']);
        $this->assertSame('Samsung', $insertCalls[0]['row']['name']);
        $this->assertSame(1, $insertCalls[0]['row']['status']);

        // 2nd and 3rd insert: pp_shop_producer_lang for each language
        $langInserts = array_filter($insertCalls, fn($c) => $c['table'] === 'pp_shop_producer_lang');
        $this->assertCount(2, $langInserts);
    }

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

        $mockDb->expects($this->atLeastOnce())
            ->method('update')
            ->willReturnCallback(function ($table, $row, $where) use (&$updateRow) {
                if ($table === 'pp_shop_producer') {
                    $updateRow = $row;
                    $this->assertSame(['id' => 7], $where);
                }
                return true;
            });

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

        // get for translation id check
        $mockDb->method('get')
            ->willReturn(100);

        $langs = [['id' => 'pl']];

        $repository = new ProducerRepository($mockDb);
        $id = $repository->save(7, 'Zaktualizowany', 0, null, ['pl' => 'Nowy opis'], ['pl' => null], ['pl' => 'Meta'], $langs);

        $this->assertSame(7, $id);
        $this->assertSame('Zaktualizowany', $updateRow['name']);
        $this->assertSame(0, $updateRow['status']);
    }

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

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

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

        $repository = new ProducerRepository($mockDb);
        $this->assertTrue($repository->delete(3));
    }

    public function testListForAdminWhitelistsSortAndPagination(): 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' => 'Test',
                            'status' => 1,
                            'img' => null,
                        ]];
                    }
                };
            });

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

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

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

    public function testAllProducersReturnsFormattedList(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('select')
            ->with('pp_shop_producer', ['id', 'name'], ['ORDER' => ['name' => 'ASC']])
            ->willReturn([
                ['id' => '1', 'name' => 'Apple'],
                ['id' => '2', 'name' => 'Samsung'],
            ]);

        $repository = new ProducerRepository($mockDb);
        $result = $repository->allProducers();

        $this->assertCount(2, $result);
        $this->assertSame(1, $result[0]['id']);
        $this->assertSame('Apple', $result[0]['name']);
        $this->assertSame(2, $result[1]['id']);
    }

    public function testProducerProductsReturnsPaginatedResults(): void
    {
        $mockDb = $this->createMock(\medoo::class);
        $mockDb->expects($this->once())
            ->method('count')
            ->willReturn(30);

        $mockDb->expects($this->once())
            ->method('select')
            ->willReturn([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);

        $repository = new ProducerRepository($mockDb);
        $result = $repository->producerProducts(5, 12, 1);

        $this->assertArrayHasKey('products', $result);
        $this->assertArrayHasKey('ls', $result);
        $this->assertSame(3, $result['ls']);
    }
}
