<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\Service;

use OCA\Mitgliederverwaltung\Db\GeneralMaterial;
use OCA\Mitgliederverwaltung\Db\GeneralMaterialMapper;
use OCA\Mitgliederverwaltung\Db\InventoryCategory;
use OCA\Mitgliederverwaltung\Db\InventoryCategoryMapper;
use OCA\Mitgliederverwaltung\Db\InventoryItemCategory;
use OCA\Mitgliederverwaltung\Db\InventoryItemCategoryMapper;
use OCA\Mitgliederverwaltung\Db\StockItem;
use OCA\Mitgliederverwaltung\Db\StockItemMapper;
use OCA\Mitgliederverwaltung\Service\AuditService;
use OCA\Mitgliederverwaltung\Service\InventoryService;
use OCP\AppFramework\Db\DoesNotExistException;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

class InventoryServiceTest extends TestCase {

    private InventoryService $service;
    private InventoryCategoryMapper&MockObject $categoryMapper;
    private InventoryItemCategoryMapper&MockObject $itemCategoryMapper;
    private GeneralMaterialMapper&MockObject $generalMaterialMapper;
    private StockItemMapper&MockObject $stockItemMapper;
    private MockObject $stockVariantMapper;
    private AuditService&MockObject $auditService;
    private LoggerInterface&MockObject $logger;

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

        $this->categoryMapper = $this->createMock(InventoryCategoryMapper::class);
        $this->itemCategoryMapper = $this->createMock(InventoryItemCategoryMapper::class);
        $this->generalMaterialMapper = $this->createMock(GeneralMaterialMapper::class);
        $this->stockItemMapper = $this->createMock(StockItemMapper::class);
        $this->stockVariantMapper = $this->getMockBuilder(\OCA\Mitgliederverwaltung\Db\StockVariantMapper::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['findByStockItemId', 'findById', 'insert', 'update', 'delete'])
            ->getMock();
        $this->auditService = $this->createMock(AuditService::class);
        $this->logger = $this->createMock(LoggerInterface::class);

        $this->service = new InventoryService(
            $this->categoryMapper,
            $this->itemCategoryMapper,
            $this->generalMaterialMapper,
            $this->stockItemMapper,
            $this->stockVariantMapper,
            $this->auditService,
            $this->logger
        );
    }

    private function generalMaterialEntity(int $id = 1, string $name = 'Zelt A', ?int $condition = 3): GeneralMaterial {
        $entity = new GeneralMaterial();
        $entity->setId($id);
        $entity->setName($name);
        $entity->setCondition($condition);
        $entity->setNotes(null);
        $entity->setCreatedAt('2026-04-21 00:00:00');
        $entity->setUpdatedAt('2026-04-21 00:00:00');
        return $entity;
    }

    private function categoryEntity(int $id = 1, string $name = 'Zelte'): InventoryCategory {
        $entity = new InventoryCategory();
        $entity->setId($id);
        $entity->setName($name);
        $entity->setCreatedAt('2026-04-21 00:00:00');
        return $entity;
    }

    private function itemCategoryEntity(int $itemId = 1, int $categoryId = 1): InventoryItemCategory {
        $entity = new InventoryItemCategory();
        $entity->setItemId($itemId);
        $entity->setCategoryId($categoryId);
        return $entity;
    }

    public function testCreateGeneralMaterial_auditLogged(): void {
        $gm = $this->generalMaterialEntity(1, 'Zelt A', 4);
        $this->generalMaterialMapper->method('insert')->willReturnCallback(function ($entity) use ($gm) {
            $entity->setId($gm->getId());
        });
        $this->categoryMapper->method('findById')->willReturn($this->categoryEntity(1, 'Zelte'));
        $this->itemCategoryMapper->method('findByItemId')->willReturn([$this->itemCategoryEntity(1, 1)]);

        $this->auditService->method('logCreate')->willReturnCallback(function(array $data, string $type, int $id) {
            $this->assertSame('Zelt A', $data['name']);
            $this->assertSame(4, $data['condition']);
            $this->assertSame('inventory_general_material', $type);
            $this->assertSame(1, $id);
        });

        $result = $this->service->createGeneralMaterial([
            'name' => 'Zelt A',
            'condition' => 4,
            'notes' => null,
            'categories' => [1],
        ]);

        $this->assertSame('Zelt A', $result->getName());
    }

    public function testCreateStockItem_auditLogged(): void {
        $this->stockItemMapper->method('insert')->willReturnCallback(function ($entity) {
            $entity->setId(1);
            $entity->setName('Pfadfinderhemd');
        });
        $this->itemCategoryMapper->method('findByItemId')->willReturn([]);

        $this->auditService->method('logCreate')->willReturnCallback(function(array $data, string $type, int $id) {
            $this->assertSame('Pfadfinderhemd', $data['name']);
            $this->assertSame('inventory_stock_item', $type);
            $this->assertSame(1, $id);
        });

        $result = $this->service->createStockItem([
            'name' => 'Pfadfinderhemd',
            'categories' => null,
        ]);

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

    public function testDelete_auditLogged(): void {
        $gm = $this->generalMaterialEntity(1, 'Zelt A');
        $this->generalMaterialMapper->method('findById')->willReturn($gm);

        $this->auditService->method('logDelete')->willReturnCallback(function(string $type, int $id) {
            $this->assertSame('inventory_general_material', $type);
            $this->assertSame(1, $id);
        });

        $this->service->deleteGeneralMaterial(1);

        $this->assertTrue(true);
    }

    public function testConditionAlerts_correctThreshold(): void {
        $this->assertFalse($this->service->needsRepair(3));
        $this->assertFalse($this->service->needsRepair(4));
        $this->assertFalse($this->service->needsRepair(5));
        $this->assertTrue($this->service->needsRepair(2));
        $this->assertTrue($this->service->needsRepair(1));
        $this->assertTrue($this->service->needsRepair(0));
        $this->assertTrue($this->service->needsRepair(null));
    }

    public function testGetConditionLabel(): void {
        $this->assertSame('Nicht bewertet', $this->service->getConditionLabel(null));
        $this->assertSame('1/5', $this->service->getConditionLabel(1));
        $this->assertSame('3/5', $this->service->getConditionLabel(3));
        $this->assertSame('5/5', $this->service->getConditionLabel(5));
    }
}
