<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Service;

use DateTime;
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\Db\StockVariant;
use OCA\Mitgliederverwaltung\Db\StockVariantMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\Exception;
use OCP\IRequest;
use Psr\Log\LoggerInterface;

/**
 * Service layer for inventory categories, general material, and stock items.
 *
 * Part of Issue #165 (Inventory Tracking).
 */
class InventoryService {

    private InventoryCategoryMapper $categoryMapper;
    private InventoryItemCategoryMapper $itemCategoryMapper;
    private GeneralMaterialMapper $generalMaterialMapper;
    private StockItemMapper $stockItemMapper;
    private StockVariantMapper $stockVariantMapper;
    private AuditService $auditService;
    private LoggerInterface $logger;

    public function __construct(
        InventoryCategoryMapper $categoryMapper,
        InventoryItemCategoryMapper $itemCategoryMapper,
        GeneralMaterialMapper $generalMaterialMapper,
        StockItemMapper $stockItemMapper,
        StockVariantMapper $stockVariantMapper,
        AuditService $auditService,
        LoggerInterface $logger
    ) {
        $this->categoryMapper = $categoryMapper;
        $this->itemCategoryMapper = $itemCategoryMapper;
        $this->generalMaterialMapper = $generalMaterialMapper;
        $this->stockItemMapper = $stockItemMapper;
        $this->stockVariantMapper = $stockVariantMapper;
        $this->auditService = $auditService;
        $this->logger = $logger;
    }

    // ── Categories ────────────────────────────────────────────────────

    /**
     * @return InventoryCategory[]
     * @throws Exception
     */
    public function getCategories(): array {
        return $this->categoryMapper->findAll();
    }

    /**
     * @throws DoesNotExistException
     * @throws Exception
     */
    public function getCategory(int $id): InventoryCategory {
        return $this->categoryMapper->findById($id);
    }

    /**
     * @throws Exception
     */
    public function createCategory(array $data): InventoryCategory {
        $category = new InventoryCategory();
        $category->setName($data['name'] ?? '');
        $category->setCreatedAt((new DateTime())->format('Y-m-d H:i:s'));
        $this->categoryMapper->insert($category);

        $this->auditService->logCreate([
            'name' => $category->getName(),
        ], 'inventory_category', $category->getId());

        return $category;
    }

    /**
     * @throws DoesNotExistException
     * @throws Exception
     */
    public function updateCategory(int $id, array $data): InventoryCategory {
        $category = $this->categoryMapper->findById($id);
        $oldData = ['name' => $category->getName()];

        if (isset($data['name'])) {
            $category->setName($data['name']);
        }

        $this->categoryMapper->update($category);

        $this->auditService->logUpdate($oldData, [
            'name' => $category->getName(),
        ], 'inventory_category', $id);

        return $category;
    }

    /**
     * @throws DoesNotExistException
     * @throws Exception
     */
    public function deleteCategory(int $id): void {
        $category = $this->categoryMapper->findById($id);
        $this->categoryMapper->delete($category);

        $this->auditService->logDelete('inventory_category', $id);
    }

    // ── General Material ──────────────────────────────────────────────

    /**
     * @return GeneralMaterial[]
     * @throws Exception
     */
    public function getGeneralMaterial(): array {
        return $this->generalMaterialMapper->findAll();
    }

    /**
     * @throws DoesNotExistException
     * @throws Exception
     */
    public function getGeneralMaterialById(int $id): GeneralMaterial {
        return $this->generalMaterialMapper->findById($id);
    }

    /**
     * @throws Exception
     */
    public function createGeneralMaterial(array $data, ?array $categoryIds = null): GeneralMaterial {
        $now = (new DateTime())->format('Y-m-d H:i:s');
        $item = new GeneralMaterial();
        $item->setName($data['name'] ?? '');
        $item->setCondition($data['condition'] ?? null);
        $item->setNotes($data['notes'] ?? null);
        $item->setCreatedAt($now);
        $item->setUpdatedAt($now);
        $this->generalMaterialMapper->insert($item);

        // Attach categories
        if ($categoryIds !== null && is_array($categoryIds)) {
            foreach ($categoryIds as $catId) {
                try {
                    $this->itemCategoryMapper->attach($item->getId(), (int)$catId);
                } catch (\Exception $e) {
                    $this->logger->warning('Failed to attach category {catId} to item {itemId}', [
                        'catId' => $catId,
                        'itemId' => $item->getId(),
                        'app' => 'mitgliederverwaltung',
                    ]);
                }
            }
        }

        $this->auditService->logCreate([
            'name' => $item->getName(),
            'condition' => $item->getCondition(),
            'notes' => $item->getNotes(),
        ], 'inventory_general_material', $item->getId());

        return $item;
    }

    /**
     * @throws DoesNotExistException
     * @throws Exception
     */
    public function updateGeneralMaterial(int $id, array $data, ?array $categoryIds = null): GeneralMaterial {
        $item = $this->generalMaterialMapper->findById($id);
        $oldData = [
            'name' => $item->getName(),
            'condition' => $item->getCondition(),
            'notes' => $item->getNotes(),
        ];

        if (isset($data['name'])) {
            $item->setName($data['name']);
        }
        if (array_key_exists('condition', $data)) {
            $item->setCondition($data['condition'] === null ? null : (int)$data['condition']);
        }
        if (array_key_exists('notes', $data)) {
            $item->setNotes($data['notes'] === null ? null : (string)$data['notes']);
        }
        $item->setUpdatedAt((new DateTime())->format('Y-m-d H:i:s'));
        $this->generalMaterialMapper->update($item);

        // Sync categories if provided
        if ($categoryIds !== null && is_array($categoryIds)) {
            // Detach all existing categories and re-attach
            $existing = $this->itemCategoryMapper->findByItemId($id);
            foreach ($existing as $existingCat) {
                $this->itemCategoryMapper->detach($id, (int)$existingCat->getCategoryId());
            }
            foreach ($categoryIds as $catId) {
                try {
                    $this->itemCategoryMapper->attach($id, (int)$catId);
                } catch (\Exception $e) {
                    $this->logger->warning('Failed to attach category {catId} to item {itemId}', [
                        'catId' => $catId,
                        'itemId' => $id,
                        'app' => 'mitgliederverwaltung',
                    ]);
                }
            }
        }

        $this->auditService->logUpdate($oldData, [
            'name' => $item->getName(),
            'condition' => $item->getCondition(),
            'notes' => $item->getNotes(),
        ], 'inventory_general_material', $id);

        return $item;
    }

    /**
     * @throws DoesNotExistException
     * @throws Exception
     */
    public function deleteGeneralMaterial(int $id): void {
        $item = $this->generalMaterialMapper->findById($id);
        $this->generalMaterialMapper->delete($item);

        $this->auditService->logDelete('inventory_general_material', $id);
    }

    /**
     * Get items needing repair (condition ≤ 2 or NULL).
     *
     * @return GeneralMaterial[]
     * @throws Exception
     */
    public function getNeedingRepair(): array {
        return $this->generalMaterialMapper->findNeedingRepair();
    }

    /**
     * @return GeneralMaterial[]
     * @throws Exception
     */
    public function getGeneralMaterialByConditionRange(?int $min, ?int $max): array {
        return $this->generalMaterialMapper->findByConditionRange($min, $max);
    }

    /**
     * Get category names for an item.
     *
     * @return string[]
     * @throws Exception
     */
    public function getCategoryNamesForItem(int $itemId): array {
        $cats = $this->itemCategoryMapper->findByItemId($itemId);
        $names = [];
        foreach ($cats as $cat) {
            try {
                $c = $this->categoryMapper->findById((int)$cat->getCategoryId());
                $names[] = $c->getName();
            } catch (\Exception $e) {
                // ignore
            }
        }
        return $names;
    }

    // ── Stock Items ───────────────────────────────────────────────────

    /**
     * @return StockItem[]
     * @throws Exception
     */
    public function getStockItems(): array {
        return $this->stockItemMapper->findAll();
    }

    /**
     * @throws DoesNotExistException
     * @throws Exception
     */
    public function getStockItemById(int $id): StockItem {
        return $this->stockItemMapper->findById($id);
    }

    /**
     * @throws Exception
     */
    public function createStockItem(array $data, ?array $categoryIds = null): StockItem {
        $now = (new DateTime())->format('Y-m-d H:i:s');
        $item = new StockItem();
        $item->setName($data['name'] ?? '');
        $item->setProviderUrlsJson($data['provider_urls_json'] ?? null);
        $item->setCreatedAt($now);
        $item->setUpdatedAt($now);
        $this->stockItemMapper->insert($item);

        // Attach categories
        if ($categoryIds !== null && is_array($categoryIds)) {
            foreach ($categoryIds as $catId) {
                try {
                    $this->itemCategoryMapper->attach($item->getId(), (int)$catId);
                } catch (\Exception $e) {
                    $this->logger->warning('Failed to attach category {catId} to stock item {itemId}', [
                        'catId' => $catId,
                        'itemId' => $item->getId(),
                        'app' => 'mitgliederverwaltung',
                    ]);
                }
            }
        }

        $this->auditService->logCreate([
            'name' => $item->getName(),
        ], 'inventory_stock_item', $item->getId());

        return $item;
    }

    /**
     * @throws DoesNotExistException
     * @throws Exception
     */
    public function updateStockItem(int $id, array $data, ?array $categoryIds = null): StockItem {
        $item = $this->stockItemMapper->findById($id);
        $oldData = [
            'name' => $item->getName(),
            'provider_urls_json' => $item->getProviderUrlsJson(),
        ];

        if (isset($data['name'])) {
            $item->setName($data['name']);
        }
        if (array_key_exists('provider_urls_json', $data)) {
            $item->setProviderUrlsJson($data['provider_urls_json'] ?? null);
        }
        $item->setUpdatedAt((new DateTime())->format('Y-m-d H:i:s'));
        $this->stockItemMapper->update($item);

        // Sync categories if provided
        if ($categoryIds !== null && is_array($categoryIds)) {
            $existing = $this->itemCategoryMapper->findByItemId($id);
            foreach ($existing as $existingCat) {
                $this->itemCategoryMapper->detach($id, (int)$existingCat->getCategoryId());
            }
            foreach ($categoryIds as $catId) {
                try {
                    $this->itemCategoryMapper->attach($id, (int)$catId);
                } catch (\Exception $e) {
                    $this->logger->warning('Failed to attach category {catId} to stock item {itemId}', [
                        'catId' => $catId,
                        'itemId' => $id,
                        'app' => 'mitgliederverwaltung',
                    ]);
                }
            }
        }

        $this->auditService->logUpdate($oldData, [
            'name' => $item->getName(),
            'provider_urls_json' => $item->getProviderUrlsJson(),
        ], 'inventory_stock_item', $id);

        return $item;
    }

    /**
     * @throws DoesNotExistException
     * @throws Exception
     */
    public function deleteStockItem(int $id): void {
        $item = $this->stockItemMapper->findById($id);
        $this->stockItemMapper->delete($item);

        $this->auditService->logDelete('inventory_stock_item', $id);
    }

    // ── Stock Variants ────────────────────────────────────────────────

    /**
     * @throws Exception
     */
    public function createStockVariant(array $data): StockVariant {
        $variant = new StockVariant();
        $variant->setStockItemId((int)$data['stock_item_id']);
        $variant->setLabel($data['label'] ?? '');
        $variant->setAmount((int)($data['amount'] ?? 0));
        $variant->setCost((string)($data['cost'] ?? '0.00'));
        $variant->setMinThreshold((int)($data['min_threshold'] ?? 0));
        $this->stockVariantMapper->insert($variant);

        $this->auditService->logCreate([
            'label' => $variant->getLabel(),
            'amount' => $variant->getAmount(),
            'cost' => $variant->getCost(),
        ], 'inventory_stock_variant', $variant->getId());

        return $variant;
    }

    /**
     * @throws DoesNotExistException
     * @throws Exception
     */
    public function updateStockVariant(int $id, array $data): StockVariant {
        $variant = $this->stockVariantMapper->findById($id);
        $oldData = [
            'label' => $variant->getLabel(),
            'amount' => $variant->getAmount(),
            'cost' => $variant->getCost(),
            'min_threshold' => $variant->getMinThreshold(),
        ];

        if (isset($data['label'])) {
            $variant->setLabel($data['label']);
        }
        if (array_key_exists('amount', $data)) {
            $variant->setAmount((int)$data['amount']);
        }
        if (array_key_exists('cost', $data)) {
            $variant->setCost((string)$data['cost']);
        }
        if (array_key_exists('min_threshold', $data)) {
            $variant->setMinThreshold((int)$data['min_threshold']);
        }

        $this->stockVariantMapper->update($variant);

        $this->auditService->logUpdate($oldData, [
            'label' => $variant->getLabel(),
            'amount' => $variant->getAmount(),
            'cost' => $variant->getCost(),
            'min_threshold' => $variant->getMinThreshold(),
        ], 'inventory_stock_variant', $id);

        return $variant;
    }

    /**
     * @throws DoesNotExistException
     * @throws Exception
     */
    public function deleteStockVariant(int $id): void {
        $variant = $this->stockVariantMapper->findById($id);
        $this->stockVariantMapper->delete($variant);

        $this->auditService->logDelete('inventory_stock_variant', $id);
    }

    /**
     * @return StockVariant[]
     * @throws Exception
     */
    public function getVariantsByStockItem(int $stockItemId): array {
        return $this->stockVariantMapper->findByStockItemId($stockItemId);
    }

    /**
     * Check if an item needs repair (condition ≤ 2).
     */
    public function needsRepair(?int $condition): bool {
        return $condition === null || $condition <= 2;
    }

    /**
     * Get condition label for display.
     */
    public function getConditionLabel(?int $condition): string {
        if ($condition === null) {
            return 'Nicht bewertet';
        }
        return $condition . '/5';
    }
}
