<?php

declare(strict_types=1);

namespace ApiModel\Service;

use ApiModel\Domain\Repository\InventoryRepositoryInterface;
use ApiModel\Infrastructure\Persistence\Database;
use ApiModel\Shared\Exceptions\BadRequestException;
use ApiModel\Shared\Exceptions\NotFoundException;

final class InventoryService
{
    public function __construct(private readonly InventoryRepositoryInterface $repository) {}

    // --- Categories ---
    public function listCategories(string $companyId): array
    {
        return $this->repository->listCategories($companyId);
    }

    public function getCategory(string $id): array
    {
        $category = $this->repository->findCategoryById($id);
        if ($category === null) {
            throw new NotFoundException('Categoría no encontrada');
        }
        return $category;
    }

    public function saveCategory(string $companyId, array $data): array
    {
        $id = trim((string) ($data['id'] ?? ''));
        $isUpdate = $id !== '' && $id !== '0';

        if ($isUpdate) {
            $existing = $this->repository->findCategoryById($id);
            if ($existing === null || (string) $existing['company_id'] !== $companyId) {
                throw new NotFoundException('Categoría no encontrada');
            }
            $data = array_merge($existing, $data);
        }

        $name = trim((string) ($data['name'] ?? ''));
        $description = isset($data['description']) ? trim((string) $data['description']) : null;

        if ($name === '') {
            throw new BadRequestException('El nombre de la categoría es obligatorio');
        }

        if ($this->repository->categoryNameExists($companyId, $name, $isUpdate ? $id : null)) {
            throw new BadRequestException('Ya existe una categoría con este nombre');
        }

        if ($isUpdate) {
            $this->repository->updateCategory($id, $name, $description);
        } else {
            $id = Database::uuid();
            $this->repository->insertCategory($id, $companyId, $name, $description);
        }

        return $this->getCategory($id);
    }

    public function deleteCategory(string $id): void
    {
        $this->getCategory($id);
        $this->repository->deleteCategory($id);
    }

    // --- Items ---
    public function listItems(string $companyId): array
    {
        return array_map(fn (array $row): array => $this->mapItemSummary($row), $this->repository->listItems($companyId));
    }

    public function getItem(string $companyId, string $id): array
    {
        $item = $this->repository->findItemById($id);
        if ($item === null || (string) $item['company_id'] !== $companyId) {
            throw new NotFoundException('Producto/Servicio no encontrado');
        }
        return $this->mapItemDetail($item);
    }

    public function saveItem(string $companyId, array $data): array
    {
        $id = trim((string) ($data['id'] ?? ''));
        $isUpdate = $id !== '' && $id !== '0';

        if ($isUpdate) {
            $existing = $this->repository->findItemById($id);
            if ($existing === null || (string) $existing['company_id'] !== $companyId) {
                throw new NotFoundException('Producto/Servicio no encontrado');
            }
            $data = array_merge([
                'category_id' => $existing['category_id'] ?? null,
                'sku' => $existing['sku'] ?? '',
                'name' => $existing['name'] ?? '',
                'description' => $existing['description'] ?? '',
                'item_type' => $existing['item_type'] ?? 'product',
                'sale_price' => $existing['sale_price'] ?? 0.0,
                'purchase_price' => $existing['purchase_price'] ?? 0.0,
                'tax_rate_id' => $existing['tax_rate_id'] ?? null,
                'income_account_id' => $existing['income_account_id'] ?? null,
                'expense_account_id' => $existing['expense_account_id'] ?? null,
                'inventory_account_id' => $existing['inventory_account_id'] ?? null,
                'is_active' => isset($existing['is_active']) ? (bool) $existing['is_active'] : true,
            ], $data);
        }

        $sku = trim((string) ($data['sku'] ?? ''));
        $name = trim((string) ($data['name'] ?? ''));
        $itemType = trim((string) ($data['item_type'] ?? 'product'));

        if ($sku === '') {
            throw new BadRequestException('El SKU es obligatorio');
        }
        if ($name === '') {
            throw new BadRequestException('El nombre del producto es obligatorio');
        }
        if (!in_array($itemType, ['product', 'service'])) {
            throw new BadRequestException('Tipo de ítem inválido');
        }

        if ($this->repository->itemSkuExists($companyId, $sku, $isUpdate ? $id : null)) {
            throw new BadRequestException('Ya existe un producto con este SKU');
        }

        // Validate accounts based on type
        if ($itemType === 'product') {
            if (empty($data['inventory_account_id'])) {
                throw new BadRequestException('La cuenta de inventario es obligatoria para productos inventariables');
            }
        }

        $itemData = [
            'id' => $isUpdate ? $id : Database::uuid(),
            'company_id' => $companyId,
            'category_id' => !empty($data['category_id']) ? trim((string) $data['category_id']) : null,
            'sku' => $sku,
            'name' => $name,
            'description' => isset($data['description']) ? trim((string) $data['description']) : null,
            'item_type' => $itemType,
            'sale_price' => (float) ($data['sale_price'] ?? 0.0),
            'purchase_price' => (float) ($data['purchase_price'] ?? 0.0),
            'tax_rate_id' => !empty($data['tax_rate_id']) ? trim((string) $data['tax_rate_id']) : null,
            'income_account_id' => !empty($data['income_account_id']) ? trim((string) $data['income_account_id']) : null,
            'expense_account_id' => !empty($data['expense_account_id']) ? trim((string) $data['expense_account_id']) : null,
            'inventory_account_id' => $itemType === 'product' && !empty($data['inventory_account_id']) ? trim((string) $data['inventory_account_id']) : null,
            'is_active' => isset($data['is_active']) ? (bool) $data['is_active'] : true,
        ];

        if ($isUpdate) {
            $this->repository->updateItem($id, $itemData);
        } else {
            $this->repository->insertItem($itemData);
        }

        return $this->getItem($companyId, $itemData['id']);
    }

    public function deleteItem(string $companyId, string $id): void
    {
        $this->getItem($companyId, $id);
        $this->repository->deleteItem($id);
    }

    // --- Warehouses ---
    public function listWarehouses(string $companyId): array
    {
        return $this->repository->listWarehouses($companyId);
    }

    public function getWarehouse(string $companyId, string $id): array
    {
        $warehouse = $this->repository->findWarehouseById($id);
        if ($warehouse === null || (string) $warehouse['company_id'] !== $companyId) {
            throw new NotFoundException('Bodega no encontrada');
        }
        return $warehouse;
    }

    public function saveWarehouse(string $companyId, array $data): array
    {
        $id = trim((string) ($data['id'] ?? ''));
        $isUpdate = $id !== '' && $id !== '0';

        if ($isUpdate) {
            $existing = $this->repository->findWarehouseById($id);
            if ($existing === null || (string) $existing['company_id'] !== $companyId) {
                throw new NotFoundException('Bodega no encontrada');
            }
            $data = array_merge([
                'name' => $existing['name'] ?? '',
                'code' => $existing['code'] ?? '',
                'address' => $existing['address'] ?? null,
                'is_active' => isset($existing['is_active']) ? (bool) $existing['is_active'] : true,
            ], $data);
        }

        $name = trim((string) ($data['name'] ?? ''));
        $code = trim((string) ($data['code'] ?? ''));
        $address = isset($data['address']) ? trim((string) $data['address']) : null;
        $isActive = isset($data['is_active']) ? (bool) $data['is_active'] : true;

        if ($name === '') {
            throw new BadRequestException('El nombre de la bodega es obligatorio');
        }
        if ($code === '') {
            throw new BadRequestException('El código de la bodega es obligatorio');
        }

        if ($this->repository->warehouseCodeExists($companyId, $code, $isUpdate ? $id : null)) {
            throw new BadRequestException('Ya existe una bodega con este código');
        }

        if ($isUpdate) {
            $this->repository->updateWarehouse($id, $name, $code, $address, $isActive);
        } else {
            $id = Database::uuid();
            $this->repository->insertWarehouse($id, $companyId, $name, $code, $address);
        }

        return $this->getWarehouse($companyId, $id);
    }

    public function deleteWarehouse(string $companyId, string $id): void
    {
        $this->getWarehouse($companyId, $id);
        $this->repository->deleteWarehouse($id);
    }

    // --- Stock ---
    public function getWarehouseStock(string $warehouseId): array
    {
        return $this->repository->listStockByWarehouse($warehouseId);
    }

    private function mapItemSummary(array $row): array
    {
        return [
            'id' => $row['id'],
            'company_id' => $row['company_id'],
            'category_id' => $row['category_id'] ?? null,
            'category_name' => $row['category_name'] ?? '',
            'sku' => $row['sku'],
            'name' => $row['name'],
            'item_type' => $row['item_type'],
            'sale_price' => (float) ($row['sale_price'] ?? 0.0),
            'purchase_price' => (float) ($row['purchase_price'] ?? 0.0),
            'tax_rate_id' => $row['tax_rate_id'] ?? null,
            'tax_name' => $row['tax_name'] ?? '',
            'tax_rate' => (float) ($row['tax_rate'] ?? 0.0),
            'is_active' => (bool) ($row['is_active'] ?? true),
        ];
    }

    private function mapItemDetail(array $row): array
    {
        return [
            ...$this->mapItemSummary($row),
            'description' => $row['description'] ?? '',
            'income_account_id' => $row['income_account_id'] ?? null,
            'expense_account_id' => $row['expense_account_id'] ?? null,
            'inventory_account_id' => $row['inventory_account_id'] ?? null,
            'stock' => $this->repository->listStockByItem((string) $row['id']),
        ];
    }
}
