<?php

declare(strict_types=1);

namespace ApiModel\Service;

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

final class InventoryTransferService
{
    public function __construct(
        private readonly InventoryTransferRepositoryInterface $repository,
        private readonly InventoryRepositoryInterface $inventory,
        private readonly PDO $pdo
    ) {}

    public function listTransfers(string $companyId): array
    {
        return $this->repository->listByCompany($companyId);
    }

    public function getTransfer(string $companyId, string $id): array
    {
        $transfer = $this->repository->findById($id);
        if ($transfer === null || (string) $transfer['company_id'] !== $companyId) {
            throw new NotFoundException('Transferencia no encontrada');
        }

        return [
            ...$transfer,
            'lines' => $this->repository->findLinesByTransferId($id),
        ];
    }

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

        $this->pdo->beginTransaction();

        try {
            $fromWarehouseId = trim((string) ($data['from_warehouse_id'] ?? ''));
            $toWarehouseId = trim((string) ($data['to_warehouse_id'] ?? ''));
            $transferDate = trim((string) ($data['transfer_date'] ?? date('Y-m-d')));
            $notes = isset($data['notes']) ? trim((string) $data['notes']) : '';
            $lines = $data['lines'] ?? [];

            if ($fromWarehouseId === '') {
                throw new BadRequestException('La bodega de origen es obligatoria');
            }
            if ($toWarehouseId === '') {
                throw new BadRequestException('La bodega de destino es obligatoria');
            }
            if ($fromWarehouseId === $toWarehouseId) {
                throw new BadRequestException('Las bodegas de origen y destino no pueden ser iguales');
            }
            if ($transferDate === '') {
                throw new BadRequestException('La fecha de transferencia es obligatoria');
            }
            if (count($lines) === 0) {
                throw new BadRequestException('La transferencia debe tener al menos una línea de detalle');
            }

            // Check warehouses exist and belong to company
            $fromWarehouse = $this->inventory->findWarehouseById($fromWarehouseId);
            if ($fromWarehouse === null || (string) $fromWarehouse['company_id'] !== $companyId) {
                throw new NotFoundException('Bodega de origen no encontrada');
            }
            $toWarehouse = $this->inventory->findWarehouseById($toWarehouseId);
            if ($toWarehouse === null || (string) $toWarehouse['company_id'] !== $companyId) {
                throw new NotFoundException('Bodega de destino no encontrada');
            }

            if ($isUpdate) {
                $existing = $this->repository->findById($id);
                if ($existing === null || (string) $existing['company_id'] !== $companyId) {
                    throw new NotFoundException('Transferencia no encontrada');
                }
                $transferNumber = (string) $existing['transfer_number'];

                // Revert stock changes of the old lines (revert from old warehouses)
                $oldFromId = (string) $existing['from_warehouse_id'];
                $oldToId = (string) $existing['to_warehouse_id'];
                $oldLines = $this->repository->findLinesByTransferId($id);

                foreach ($oldLines as $oldLine) {
                    $itemId = (string) $oldLine['item_id'];
                    $qty = (float) $oldLine['quantity'];

                    $fromStock = $this->inventory->getStock($itemId, $oldFromId);
                    $this->inventory->updateStock($itemId, $oldFromId, $fromStock + $qty);

                    $toStock = $this->inventory->getStock($itemId, $oldToId);
                    $this->inventory->updateStock($itemId, $oldToId, $toStock - $qty);
                }

                $this->repository->deleteLines($id);
            } else {
                $id = Database::uuid();
                $transferNumber = $this->repository->nextTransferNumber($companyId);
            }

            $processedLines = [];

            foreach ($lines as $line) {
                $itemId = trim((string) ($line['item_id'] ?? ''));
                $quantity = (float) ($line['quantity'] ?? 0.0);

                if ($itemId === '') {
                    throw new BadRequestException('El producto es obligatorio en cada línea');
                }
                if ($quantity <= 0.0) {
                    throw new BadRequestException('La cantidad debe ser mayor a cero');
                }

                // Check item exists
                $item = $this->inventory->findItemById($itemId);
                if ($item === null || (string) $item['company_id'] !== $companyId) {
                    throw new NotFoundException('Producto no encontrado');
                }

                if ((string) $item['item_type'] !== 'product') {
                    throw new BadRequestException('Solo se pueden transferir productos físicos (no servicios)');
                }

                // Check stock sufficient in from_warehouse
                $fromStock = $this->inventory->getStock($itemId, $fromWarehouseId);
                if ($fromStock < $quantity) {
                    throw new BadRequestException("Stock insuficiente para el producto {$item['name']} (SKU: {$item['sku']}) en la bodega de origen. Disponible: {$fromStock}, Requerido: {$quantity}.");
                }

                // Apply physical stock changes
                $this->inventory->updateStock($itemId, $fromWarehouseId, $fromStock - $quantity);
                
                $toStock = $this->inventory->getStock($itemId, $toWarehouseId);
                $this->inventory->updateStock($itemId, $toWarehouseId, $toStock + $quantity);

                $processedLines[] = [
                    'id' => Database::uuid(),
                    'item_id' => $itemId,
                    'quantity' => $quantity,
                ];
            }

            $transferObj = [
                'id' => $id,
                'company_id' => $companyId,
                'branch_id' => $branchId,
                'transfer_number' => $transferNumber,
                'transfer_date' => $transferDate,
                'from_warehouse_id' => $fromWarehouseId,
                'to_warehouse_id' => $toWarehouseId,
                'notes' => $notes,
                'created_by' => $userId,
            ];

            if ($isUpdate) {
                $this->repository->delete($id);
            }
            $this->repository->insert($transferObj);
            $this->repository->insertLines($id, $processedLines);

            $this->pdo->commit();

            return $this->getTransfer($companyId, $id);
        } catch (\Exception $e) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $e;
        }
    }

    public function deleteTransfer(string $companyId, string $id): void
    {
        $transfer = $this->repository->findById($id);
        if ($transfer === null || (string) $transfer['company_id'] !== $companyId) {
            throw new NotFoundException('Transferencia no encontrada');
        }

        $this->pdo->beginTransaction();

        try {
            $lines = $this->repository->findLinesByTransferId($id);
            $fromWarehouseId = (string) $transfer['from_warehouse_id'];
            $toWarehouseId = (string) $transfer['to_warehouse_id'];

            // Revert stock changes
            foreach ($lines as $line) {
                $itemId = (string) $line['item_id'];
                $qty = (float) $line['quantity'];

                $fromStock = $this->inventory->getStock($itemId, $fromWarehouseId);
                $this->inventory->updateStock($itemId, $fromWarehouseId, $fromStock + $qty);

                $toStock = $this->inventory->getStock($itemId, $toWarehouseId);
                $this->inventory->updateStock($itemId, $toWarehouseId, $toStock - $qty);
            }

            $this->repository->deleteLines($id);
            $this->repository->delete($id);

            $this->pdo->commit();
        } catch (\Exception $e) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $e;
        }
    }
}
