<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Auth;
use App\Core\Database;
use App\Core\Tenant;
use RuntimeException;

class InventoryTransferService
{
    private ?bool $variantTableAvailable = null;
    /** @var array<string,bool> */
    private array $columnAvailabilityCache = [];

    public function transferBetweenBranches(array $payload): string
    {
        $companyId = (int) (Tenant::companyId() ?? 0);
        $userId = (int) (Auth::id() ?? 0);

        if ($companyId <= 0 || $userId <= 0) {
            throw new RuntimeException('Sesión inválida para registrar traslados de inventario.');
        }

        $productId = (int) ($payload['producto_id'] ?? 0);
        $variantId = (int) ($payload['variante_id'] ?? 0);
        $sourceBranchId = (int) ($payload['sucursal_origen_id'] ?? 0);
        $targetBranchId = (int) ($payload['sucursal_destino_id'] ?? 0);
        $quantity = (float) ($payload['cantidad'] ?? 0);
        $notes = trim((string) ($payload['observacion'] ?? ''));

        if ($productId <= 0) {
            throw new RuntimeException('Debes seleccionar un producto para trasladar.');
        }

        if ($sourceBranchId <= 0 || $targetBranchId <= 0 || $sourceBranchId === $targetBranchId) {
            throw new RuntimeException('Selecciona sucursal origen y destino válidas.');
        }

        if ($quantity <= 0) {
            throw new RuntimeException('La cantidad a trasladar debe ser mayor a cero.');
        }

        return Database::transaction(function () use (
            $companyId,
            $productId,
            $variantId,
            $sourceBranchId,
            $targetBranchId,
            $quantity,
            $notes
        ): string {
            $pdo = Database::connection();

            $branchStmt = $pdo->prepare(
                'SELECT id, nombre, activa
                 FROM sucursales
                 WHERE id = :sucursal_id
                   AND empresa_id = :empresa_id
                 LIMIT 1
                 FOR UPDATE'
            );

            $branchStmt->execute([
                ':sucursal_id' => $sourceBranchId,
                ':empresa_id' => $companyId,
            ]);
            $sourceBranch = $branchStmt->fetch();

            if (!$sourceBranch || (int) ($sourceBranch['activa'] ?? 0) !== 1) {
                throw new RuntimeException('La sucursal origen no es válida o está inactiva.');
            }

            $branchStmt->execute([
                ':sucursal_id' => $targetBranchId,
                ':empresa_id' => $companyId,
            ]);
            $targetBranch = $branchStmt->fetch();

            if (!$targetBranch || (int) ($targetBranch['activa'] ?? 0) !== 1) {
                throw new RuntimeException('La sucursal destino no es válida o está inactiva.');
            }

            $productStmt = $pdo->prepare(
                'SELECT id, nombre, activo, sucursal_id, precio_compra, stock_actual
                 FROM productos
                 WHERE id = :producto_id
                   AND empresa_id = :empresa_id
                 LIMIT 1
                 FOR UPDATE'
            );
            $productStmt->execute([
                ':producto_id' => $productId,
                ':empresa_id' => $companyId,
            ]);
            $product = $productStmt->fetch();

            if (!$product) {
                throw new RuntimeException('El producto seleccionado no existe.');
            }

            if ((int) ($product['activo'] ?? 0) !== 1) {
                throw new RuntimeException('El producto seleccionado está inactivo.');
            }

            $hasVariantTable = $this->hasVariantTable($pdo);
            $hasMovementVariantColumn = $this->tableHasColumn($pdo, 'movimientos_inventario', 'variante_id');
            $variantSelectStmt = null;
            $productVariantCountStmt = null;

            if ($hasVariantTable) {
                $variantSelectStmt = $pdo->prepare(
                    'SELECT id, nombre, activo
                     FROM producto_variantes
                     WHERE id = :variante_id
                       AND empresa_id = :empresa_id
                       AND producto_id = :producto_id
                     LIMIT 1
                     FOR UPDATE'
                );

                $productVariantCountStmt = $pdo->prepare(
                    'SELECT COUNT(*)
                     FROM producto_variantes
                     WHERE empresa_id = :empresa_id
                       AND producto_id = :producto_id
                       AND activo = 1'
                );
            }

            $hasActiveVariants = false;
            if ($productVariantCountStmt !== null) {
                $productVariantCountStmt->execute([
                    ':empresa_id' => $companyId,
                    ':producto_id' => $productId,
                ]);
                $hasActiveVariants = (int) $productVariantCountStmt->fetchColumn() > 0;
            }

            if ($hasActiveVariants && $variantId <= 0) {
                throw new RuntimeException('Debes seleccionar una variación para trasladar este producto variable.');
            }

            if ($variantId > 0) {
                if (!$hasVariantTable || !$variantSelectStmt) {
                    throw new RuntimeException('El módulo de variaciones no está disponible en la base de datos.');
                }

                $variantSelectStmt->execute([
                    ':variante_id' => $variantId,
                    ':empresa_id' => $companyId,
                    ':producto_id' => $productId,
                ]);
                $variant = $variantSelectStmt->fetch();
                if (!$variant) {
                    throw new RuntimeException('La variación seleccionada no existe para el producto.');
                }

                if ((int) ($variant['activo'] ?? 0) !== 1) {
                    throw new RuntimeException('La variación seleccionada está inactiva.');
                }

                if (!$hasMovementVariantColumn) {
                    throw new RuntimeException(
                        'No puedes trasladar variaciones sin columna variante_id en movimientos_inventario. Ejecuta la migración 025.'
                    );
                }
            }

            $branchStockStmt = $pdo->prepare(
                'SELECT id, stock
                 FROM inventario
                 WHERE empresa_id = :empresa_id
                   AND sucursal_id = :sucursal_id
                   AND producto_id = :producto_id
                 LIMIT 1
                 FOR UPDATE'
            );

            $branchRowsCountStmt = $pdo->prepare(
                'SELECT COUNT(*)
                 FROM inventario
                 WHERE empresa_id = :empresa_id
                   AND producto_id = :producto_id
                   AND sucursal_id IS NOT NULL'
            );

            $seedBranchStockStmt = $pdo->prepare(
                'INSERT INTO inventario (
                    empresa_id, sucursal_id, producto_id, stock, costo_promedio, updated_at
                ) VALUES (
                    :empresa_id, :sucursal_id, :producto_id, :stock, :costo_promedio, NOW()
                )
                ON DUPLICATE KEY UPDATE
                    updated_at = VALUES(updated_at)'
            );

            $branchStockStmt->execute([
                ':empresa_id' => $companyId,
                ':sucursal_id' => $sourceBranchId,
                ':producto_id' => $productId,
            ]);
            $sourceStock = $branchStockStmt->fetch();

            if (!$sourceStock) {
                $branchRowsCountStmt->execute([
                    ':empresa_id' => $companyId,
                    ':producto_id' => $productId,
                ]);
                $hasBranchRows = (int) $branchRowsCountStmt->fetchColumn() > 0;
                $productBranchId = $product['sucursal_id'] !== null ? (int) $product['sucursal_id'] : null;

                if (!$hasBranchRows && ($productBranchId === null || $productBranchId === $sourceBranchId)) {
                    $seedBranchStockStmt->execute([
                        ':empresa_id' => $companyId,
                        ':sucursal_id' => $sourceBranchId,
                        ':producto_id' => $productId,
                        ':stock' => (float) ($product['stock_actual'] ?? 0),
                        ':costo_promedio' => (float) ($product['precio_compra'] ?? 0),
                    ]);

                    $branchStockStmt->execute([
                        ':empresa_id' => $companyId,
                        ':sucursal_id' => $sourceBranchId,
                        ':producto_id' => $productId,
                    ]);
                    $sourceStock = $branchStockStmt->fetch();
                }
            }

            $availableInSource = $sourceStock ? (float) ($sourceStock['stock'] ?? 0) : 0.0;
            if ($availableInSource + 0.0001 < $quantity) {
                throw new RuntimeException(
                    'Stock insuficiente en la sucursal origen para trasladar "'
                    . (string) ($product['nombre'] ?? 'N/A')
                    . '".'
                );
            }

            $seedBranchStockStmt->execute([
                ':empresa_id' => $companyId,
                ':sucursal_id' => $targetBranchId,
                ':producto_id' => $productId,
                ':stock' => 0,
                ':costo_promedio' => (float) ($product['precio_compra'] ?? 0),
            ]);

            $reference = 'TRASLADO#'
                . date('YmdHis')
                . '-'
                . str_pad((string) random_int(1, 9999), 4, '0', STR_PAD_LEFT);

            $discountSourceStmt = $pdo->prepare(
                'UPDATE inventario
                 SET stock = stock - :cantidad,
                     updated_at = NOW()
                 WHERE empresa_id = :empresa_id
                   AND sucursal_id = :sucursal_id
                   AND producto_id = :producto_id'
            );
            $discountSourceStmt->execute([
                ':cantidad' => $quantity,
                ':empresa_id' => $companyId,
                ':sucursal_id' => $sourceBranchId,
                ':producto_id' => $productId,
            ]);

            $increaseTargetStmt = $pdo->prepare(
                'UPDATE inventario
                 SET stock = stock + :cantidad,
                     updated_at = NOW()
                 WHERE empresa_id = :empresa_id
                   AND sucursal_id = :sucursal_id
                   AND producto_id = :producto_id'
            );
            $increaseTargetStmt->execute([
                ':cantidad' => $quantity,
                ':empresa_id' => $companyId,
                ':sucursal_id' => $targetBranchId,
                ':producto_id' => $productId,
            ]);

            $movementSql = $hasMovementVariantColumn
                ? 'INSERT INTO movimientos_inventario (
                    empresa_id, sucursal_id, producto_id, variante_id, tipo, referencia, cantidad, costo_unitario, created_at
                ) VALUES (
                    :empresa_id, :sucursal_id, :producto_id, :variante_id, :tipo, :referencia, :cantidad, :costo_unitario, NOW()
                )'
                : 'INSERT INTO movimientos_inventario (
                    empresa_id, sucursal_id, producto_id, tipo, referencia, cantidad, costo_unitario, created_at
                ) VALUES (
                    :empresa_id, :sucursal_id, :producto_id, :tipo, :referencia, :cantidad, :costo_unitario, NOW()
                )';
            $movementStmt = $pdo->prepare($movementSql);

            $unitCost = (float) ($product['precio_compra'] ?? 0);
            $movementReference = $reference;
            if ($notes !== '') {
                $movementReference .= ' | ' . substr($notes, 0, 70);
            }

            $movementParams = [
                ':empresa_id' => $companyId,
                ':sucursal_id' => $sourceBranchId,
                ':producto_id' => $productId,
                ':tipo' => 'salida',
                ':referencia' => $movementReference,
                ':cantidad' => $quantity,
                ':costo_unitario' => $unitCost,
            ];
            if ($hasMovementVariantColumn) {
                $movementParams[':variante_id'] = $variantId > 0 ? $variantId : null;
            }
            $movementStmt->execute($movementParams);

            $movementParams = [
                ':empresa_id' => $companyId,
                ':sucursal_id' => $targetBranchId,
                ':producto_id' => $productId,
                ':tipo' => 'entrada',
                ':referencia' => $movementReference,
                ':cantidad' => $quantity,
                ':costo_unitario' => $unitCost,
            ];
            if ($hasMovementVariantColumn) {
                $movementParams[':variante_id'] = $variantId > 0 ? $variantId : null;
            }
            $movementStmt->execute($movementParams);

            return $reference;
        });
    }

    private function hasVariantTable(\PDO $pdo): bool
    {
        if ($this->variantTableAvailable !== null) {
            return $this->variantTableAvailable;
        }

        $stmt = $pdo->prepare(
            "SELECT COUNT(*) FROM information_schema.TABLES
             WHERE TABLE_SCHEMA = DATABASE()
               AND TABLE_NAME = 'producto_variantes'"
        );
        $stmt->execute();

        $this->variantTableAvailable = (int) $stmt->fetchColumn() > 0;
        return $this->variantTableAvailable;
    }

    private function tableHasColumn(\PDO $pdo, string $table, string $column): bool
    {
        $cacheKey = $table . '.' . $column;
        if (array_key_exists($cacheKey, $this->columnAvailabilityCache)) {
            return $this->columnAvailabilityCache[$cacheKey];
        }

        $stmt = $pdo->prepare(
            "SELECT COUNT(*) FROM information_schema.COLUMNS
             WHERE TABLE_SCHEMA = DATABASE()
               AND TABLE_NAME = :table
               AND COLUMN_NAME = :column"
        );
        $stmt->execute([
            ':table' => $table,
            ':column' => $column,
        ]);

        $this->columnAvailabilityCache[$cacheKey] = (int) $stmt->fetchColumn() > 0;
        return $this->columnAvailabilityCache[$cacheKey];
    }
}
