<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Db;

use OCA\Mitgliederverwaltung\Db\StockItem;
use OCA\Mitgliederverwaltung\Db\StockItemMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\DB\QueryBuilder\IExpressionBuilder;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\IResult;
use OCP\IDBConnection;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class StockItemMapperTest extends TestCase {

    private IDBConnection&MockObject $db;
    private IQueryBuilder&MockObject $qb;
    private IExpressionBuilder&MockObject $expr;
    private IResult&MockObject $result;

    protected function setUp(): void {
        parent::setUp();

        $this->db = $this->createMock(IDBConnection::class);
        $this->qb = $this->createMock(IQueryBuilder::class);
        $this->expr = $this->createMock(IExpressionBuilder::class);
        $this->result = $this->createMock(IResult::class);

        $this->qb->method('expr')->willReturn($this->expr);
        $this->qb->method('select')->willReturnSelf();
        $this->qb->method('from')->willReturnSelf();
        $this->qb->method('where')->willReturnSelf();
        $this->qb->method('orderBy')->willReturnSelf();
        $this->qb->method('createNamedParameter')->willReturn(':param');
        $this->qb->method('createFunction')->willReturnCallback(fn($call) => $call);
        $this->qb->method('getSQL')->willReturn('SELECT * FROM test');

        $this->expr->method('eq')->willReturn('col = :param');
        $this->expr->method('isNull')->willReturn('col IS NULL');

        $this->db->method('getQueryBuilder')->willReturn($this->qb);
    }

    private function configureResultRows(array $rows): void {
        $fetchCalls = array_merge($rows, [false]);
        $this->result->method('fetch')
            ->willReturnOnConsecutiveCalls(...$fetchCalls);
        $this->result->method('closeCursor')->willReturn(true);
        $this->qb->method('executeQuery')->willReturn($this->result);
    }

    private function configureResultSingleRow(array $row): void {
        $this->result->method('fetch')
            ->willReturnOnConsecutiveCalls($row, false);
        $this->result->method('closeCursor')->willReturn(true);
        $this->qb->method('executeQuery')->willReturn($this->result);
    }

    private function configureResultEmpty(): void {
        $this->result->method('fetch')->willReturn(false);
        $this->result->method('closeCursor')->willReturn(true);
        $this->qb->method('executeQuery')->willReturn($this->result);
    }

    private function stockRow(int $id = 1, string $name = 'Pfadfinderhemd', ?string $urls = '[{"url":"https://shop.example.com","provider":"shop"}]'): array {
        return [
            'id' => $id,
            'name' => $name,
            'provider_urls_json' => $urls,
            'created_at' => '2026-04-21 00:00:00',
            'updated_at' => '2026-04-21 00:00:00',
        ];
    }

    public function testCreateAndFind(): void {
        $this->configureResultSingleRow($this->stockRow(42, 'Pfadfinderhemd'));
        $mapper = new StockItemMapper($this->db);
        $item = $mapper->findById(42);
        $this->assertSame(42, $item->getId());
        $this->assertSame('Pfadfinderhemd', $item->getName());
    }

    public function testFindAll(): void {
        $this->configureResultRows([
            $this->stockRow(1, 'Pfadfinderhemd'),
            $this->stockRow(2, 'Pfadfindershirt'),
        ]);
        $mapper = new StockItemMapper($this->db);
        $items = $mapper->findAll();
        $this->assertCount(2, $items);
        $this->assertSame('Pfadfinderhemd', $items[0]->getName());
    }

    public function testProviderUrlsJsonSerialization(): void {
        $this->configureResultSingleRow($this->stockRow(1, 'Hemd', '[{"url":"https://shop.example.com","provider":"shop"},{"url":"https://bestell.example.com","provider":"bestell"}]'));
        $mapper = new StockItemMapper($this->db);
        $item = $mapper->findById(1);
        $json = $item->getProviderUrlsJson();
        $this->assertNotNull($json);
        $decoded = json_decode($json, true);
        $this->assertCount(2, $decoded);
        $this->assertSame('https://shop.example.com', $decoded[0]['url']);
        $this->assertSame('shop', $decoded[0]['provider']);
    }
}
