<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Db;

use OCA\Mitgliederverwaltung\Db\SaleRecord;
use OCA\Mitgliederverwaltung\Db\SaleRecordMapper;
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 SaleRecordMapperTest 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('selectDistinct')->willReturnSelf();
        $this->qb->method('from')->willReturnSelf();
        $this->qb->method('where')->willReturnSelf();
        $this->qb->method('andWhere')->willReturnSelf();
        $this->qb->method('orderBy')->willReturnSelf();
        $this->qb->method('insert')->willReturnSelf();
        $this->qb->method('delete')->willReturnSelf();
        $this->qb->method('values')->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('gte')->willReturn('col >= :param');
        $this->expr->method('lte')->willReturn('col <= :param');
        $this->expr->method('isNull')->willReturn('col IS NULL');
        $this->expr->method('neq')->willReturn('col != :param');
        $this->expr->method('orX')->willReturn('or_expr');

        $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 configureResultCount(int $count): void {
        $this->result->method('fetchOne')->willReturn((string)$count);
        $this->result->method('closeCursor')->willReturn(true);
        $this->qb->method('executeQuery')->willReturn($this->result);
    }

    private function saleRow(int $id = 1, ?int $stockItemId = 1, ?int $variantId = 1, string $date = '2026-04-21', int $quantity = 2, string $unitPrice = '25.00', string $totalPrice = '50.00'): array {
        return [
            'id' => $id,
            'stock_item_id' => $stockItemId,
            'variant_id' => $variantId,
            'date' => $date,
            'quantity' => $quantity,
            'unit_price' => $unitPrice,
            'total_price' => $totalPrice,
            'notes' => null,
            'created_at' => '2026-04-21 00:00:00',
        ];
    }

    public function testCreateAndFind(): void {
        $this->configureResultSingleRow($this->saleRow(42, 1, 1, '2026-04-21', 2, '25.00', '50.00'));
        $mapper = new SaleRecordMapper($this->db);
        $sale = $mapper->findById(42);
        $this->assertSame(42, $sale->getId());
        $this->assertSame(2, $sale->getQuantity());
        $this->assertSame('50.00', $sale->getTotalPrice());
    }

    public function testFindByDateRange(): void {
        $this->configureResultRows([
            $this->saleRow(1, 1, null, '2026-03-15', 1, '30.00', '30.00'),
            $this->saleRow(2, 1, 2, '2026-04-01', 2, '25.00', '50.00'),
        ]);
        $mapper = new SaleRecordMapper($this->db);
        $sales = $mapper->findByDateRange('2026-03-01', '2026-04-30');
        $this->assertCount(2, $sales);
        $this->assertSame('2026-04-01', $sales[0]->getDate());
    }

    public function testFindByDateRangeEmpty(): void {
        $this->configureResultRows([]);
        $mapper = new SaleRecordMapper($this->db);
        $sales = $mapper->findByDateRange('2000-01-01', '2000-12-31');
        $this->assertCount(0, $sales);
    }

    public function testTotalPriceCalculation(): void {
        $this->configureResultSingleRow($this->saleRow(1, 1, 1, '2026-04-01', 3, '15.00', '45.00'));
        $mapper = new SaleRecordMapper($this->db);
        $sale = $mapper->findById(1);
        $this->assertSame(3, $sale->getQuantity());
        $this->assertSame('15.00', $sale->getUnitPrice());
        $this->assertSame('45.00', $sale->getTotalPrice());
        // Verify: 3 × 15.00 = 45.00
        $this->assertSame((float)$sale->getTotalPrice(), (float)$sale->getQuantity() * (float)$sale->getUnitPrice());
    }
}
