<?php

require_once __DIR__ . '/../database/database.php';

class servicioModel {

    private PDO $db;
    private ?string $error = null;

    public function __construct() {
        $this->db = Database::getConnection();
    }

    // MÉTODO PARA OBTENER ERRORES Y MANEJARLOS EN EL CONTROLLER

    public function getError(): ?string {
        return $this->error;
    }

    // GUARDADO INCIAL DEL SERVICIO
    
    public function guardarServicio(int $numero, array $datosServicio, array $cliente, array $vendedor): bool {

        if (!isset($numero)) {
            $this->error = 'No se pudo obtener el código de servicio.';
            return false;
        }

        if (empty($datosServicio)) {
            $this->error = 'El servicio no posee datos.';
            return false;
        }

        $sql = "INSERT INTO servicios 
                (id, equipo, contrasenia_equipo, problema, observacion, cuenta, contrasenia_cuenta, id_cliente, id_usuario)
                VALUES 
                (:id, :equipo, :contrasenia_equipo, :problema, :observacion, :cuenta, :contrasenia_cuenta, :id_cliente, :id_usuario)";

        $stmt = $this->db->prepare($sql);

        return (bool) $stmt->execute([
            ':id' => $numero,
            ':equipo' => $datosServicio['descripcion_equipo'],
            ':contrasenia_equipo' => $datosServicio['contrasenia_equipo'] ?? null,
            ':problema' => $datosServicio['descripcion_problema'],
            ':observacion' => ($datosServicio['observacion'] ?? '') !== '' ? $datosServicio['observacion'] : null,
            ':cuenta' => ($datosServicio['cuenta'] ?? '') !== '' ? $datosServicio['cuenta'] : null,
            ':contrasenia_cuenta' => $datosServicio['contrasenia_cuenta'] ?? null,
            ':id_cliente' => $cliente['id'],
            ':id_usuario' => $vendedor['id']
        ]);
    }

    // OBTENER PREVIEW DE CODIGO DE SERVICIO

    public function getNumeroServicioPreview(): int {

        $sql = "SELECT nextval('servicio_preview_seq')";
        $stmt = $this->db->prepare($sql);
        $stmt->execute();

        return (int) $stmt->fetchColumn();
    }

    // LISTAR SERVICIOS

    public function listarServicios(int $anio): ?array {

        $sql = "SELECT s.id, s.fecha_registro, s.fecha_terminado, s.equipo, s.estado, c.nombre AS cliente_nombre, c.apellido AS cliente_apellido, u.nombre AS vendedor_nombre, u.apellido AS vendedor_apellido FROM servicios AS s
                    INNER JOIN clientes AS c ON (s.id_cliente = c.id)
                    INNER JOIN usuarios AS u ON (s.id_usuario = u.id)
                    WHERE EXTRACT(YEAR FROM s.fecha_registro) = :anio
                ORDER BY s.id ASC";

        $stmt = $this->db->prepare($sql);

        $stmt->execute([':anio' => $anio]);

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    // OBTENER DETALLE DEL SERVICIO
    
    public function getDetalleServicio(int $idServicio): ?array {

        $sql = "SELECT id, equipo, contrasenia_equipo, problema, observacion, cuenta, contrasenia_cuenta, detalle_realizado, mano_obra, total, estado FROM servicios
                    WHERE (id = :id)";

        $stmt = $this->db->prepare($sql);

        $stmt->execute([':id' => $idServicio]);

        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    // OBTENER DETALLE DE INSUMOS UTILIZADOS EN SERVICIO

    public function getDetalleInsumos(int $idServicio): ?array {

        $sql = "SELECT p.id AS id_insumo, p.marca, p.modelo, p.stock, sd.precio_unitario, sd.cantidad, sd.subtotal, sd.id_pedido FROM servicio_detalle AS sd
                    INNER JOIN productos AS p ON (sd.id_producto = p.id)
                    WHERE (sd.id_servicio = :id_servicio)
                ORDER BY p.id ASC";

        $stmt = $this->db->prepare($sql);

        $stmt->execute([':id_servicio' => $idServicio]);

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    // ACTUALIZAR SERVICIO

    public function actualizarServicio(array $datosServicio, array $insumos): bool {

        // Validaciones (ya verifica el controller también)

        if (empty($datosServicio) || $datosServicio['id_servicio'] <= 0) {
            $this->error = 'Datos de servicio inválidos.';
            return false;
        }

        if (!isset($datosServicio['mano_obra']) || $datosServicio['mano_obra'] < 0) {
            $this->error = 'Mano de obra inválida.';
            return false;
        }

        if ($datosServicio['total_final'] < 0) {
            $this->error = 'Total inválido.';
            return false;
        }

        foreach ($insumos as $i) {
            if (!empty($insumos) && (empty($i['id_producto']) || empty($i['precio']) || empty($i['cantidad']))) {
                $this->error = 'Producto inválido en el pedido.';
                return false;
            }

            if (!empty($insumos) && ($i['cantidad'] <= 0 || $i['precio'] < 0)) {
                $this->error = 'Cantidad o precio inválido.';
                return false;
            }

            if (!empty($insumos) && $i['stock'] < 0) {
                $this->error = 'Ocurrió un error en el stock.';
                return false;
            }
        }

        try {
            $this->db->beginTransaction();

            // 1. ACTUALIZAR EL SERVICIO
            
            $sqlServicio = "UPDATE servicios 
                SET detalle_realizado = :detalle_realizado,
                    mano_obra = :mano_obra,
                    total = :total,
                    estado = 'pendiente - enviado' -- SE ACTUALIZA EL ESTADO DEL SERVICIO A (PENDIENTE - ENVIADO)
                WHERE id = :id
            ";

            $stmtServicio = $this->db->prepare($sqlServicio);

            $stmtServicio->execute([
                ':detalle_realizado' => $datosServicio['detalle_realizado'],
                ':mano_obra' => $datosServicio['mano_obra'],
                ':total' => $datosServicio['total_final'],
                ':id' => $datosServicio['id_servicio'],
            ]);

            // 2. PREPARAR QUERIES AUXILIARES

            // Pedidos disponibles (FIFO)

            $sqlPedidos = "SELECT id_pedido, (cantidad - cantidad_vendida) AS disponible FROM pedido_detalle
                WHERE id_producto = :id_producto
                AND (cantidad - cantidad_vendida) > 0
                ORDER BY id_pedido ASC
            ";

            $stmtPedidos = $this->db->prepare($sqlPedidos);

            // Insert Detalle Servicio
            
            $sqlDetalle = "INSERT INTO servicio_detalle (precio_unitario, cantidad, subtotal, id_servicio, id_producto, id_pedido) VALUES
                (:precio_unitario, :cantidad, :subtotal, :id_servicio, :id_producto, :id_pedido)
            ";

            $stmtDetalle = $this->db->prepare($sqlDetalle);

            // Update Pedido Detalle

            $sqlUpdatePedido = "UPDATE pedido_detalle
                SET cantidad_vendida = cantidad_vendida + :cantidad
                WHERE id_pedido = :id_pedido
                AND id_producto = :id_producto
            ";

            $stmtUpdatePedido = $this->db->prepare($sqlUpdatePedido);

            // Update stock del producto

            $sqlStock = "UPDATE productos
                SET stock = stock - :cantidad
                WHERE id = :id_producto
            ";

            $stmtStock = $this->db->prepare($sqlStock);

            // 3. PROCESAR DETALLE + CONSUMO STOCK (FIFO)

            if (!empty($insumos)) {

                foreach ($insumos as $p) {

                    $cantidadRestante = $p['cantidad'];

                    $stmtPedidos->execute([
                        ':id_producto' => $p['id_producto']
                    ]);

                    $pedidos = $stmtPedidos->fetchAll(PDO::FETCH_ASSOC);

                    if (empty($pedidos)) {
                        $this->db->rollBack();
                        $this->error = "No hay stock disponible para el insumo ID {$p['id_producto']}";
                        return false;
                    }

                    foreach ($pedidos as $pedido) {

                        if ($cantidadRestante <= 0) {
                            break;
                        }

                        $aVender = min($cantidadRestante, $pedido['disponible']);
                        $subtotal = $p['precio'] * $aVender;

                        // Insert venta_detalle

                        $stmtDetalle->execute([
                            ':precio_unitario' => $p['precio'],
                            ':cantidad' => $aVender,
                            ':subtotal' => $subtotal,
                            ':id_servicio' => $datosServicio['id_servicio'],
                            ':id_producto' => $p['id_producto'],
                            ':id_pedido' => $pedido['id_pedido'],
                        ]);

                        // Update pedido_detalle

                        $stmtUpdatePedido->execute([
                            ':cantidad' => $aVender,
                            ':id_pedido' => $pedido['id_pedido'],
                            ':id_producto' => $p['id_producto'],
                        ]);

                        // Update stock producto

                        $stmtStock->execute([
                            ':cantidad' => $aVender,
                            ':id_producto' => $p['id_producto'],
                        ]);

                        $cantidadRestante -= $aVender;
                    }

                    // VALIDACIÓN IMPORTANTE DE STOCK

                    if ($cantidadRestante > 0) {
                        $this->db->rollBack();
                        $this->error = "Stock insuficiente para el insumo ID {$p['id_producto']}";
                        return false;
                    }
                }
            }

            // 4. COMMIT

            $this->db->commit();
            return true;

        } catch (Throwable $e) {
            $this->db->rollBack();

            // Log interno

            error_log($e->getMessage());

            $this->error = 'Error interno al actualizar el servicio.';
            return false;
        }
    }

    // FINALIZAR SERVICIO
    
    public function finalizarServicio(int $idServicio): bool {

        if ($idServicio <= 0) {
            $this->error = 'Servicio inválido.';
            return false;
        }

        try {

            $stmt = $this->db->prepare(
                "UPDATE servicios 
                SET estado = 'finalizado',
                    fecha_terminado = CURRENT_DATE
                WHERE id = :id"
            );

            return $stmt->execute([
                ':id' => $idServicio
            ]);

        } catch (Throwable $e) {
            $this->error = 'Error interno al finalizar el servicio.';
            return false;
        }
    }

    // CANCELAR SERVICIO

    public function cancelarServicio(int $idServicio, float $cargoCancelacion): bool {

        if ($idServicio <= 0) {
            $this->error = 'Servicio inválido.';
            return false;
        }

        try {

            $this->db->beginTransaction();

            // 1. OBTENER INSUMOS DEL SERVICIO

            $sqlDetalle = "SELECT id_producto, cantidad, id_pedido FROM servicio_detalle WHERE id_servicio = :id";

            $stmtDetalle = $this->db->prepare($sqlDetalle);

            $stmtDetalle->execute([
                ':id' => $idServicio
            ]);

            $insumos = $stmtDetalle->fetchAll(PDO::FETCH_ASSOC);

            // 2. PREPARAR QUERIES

            $sqlStock = "UPDATE productos
                        SET stock = stock + :cantidad
                        WHERE id = :id_producto";

            $stmtStock = $this->db->prepare($sqlStock);

            $sqlPedido = "UPDATE pedido_detalle
                        SET cantidad_vendida = cantidad_vendida - :cantidad
                            WHERE id_pedido = :id_pedido
                        AND id_producto = :id_producto";

            $stmtPedido = $this->db->prepare($sqlPedido);

            // 3. REVERTIR INVENTARIO
            
            if (!empty($insumo)) {

                foreach ($insumos as $i) {

                    // devolver stock

                    $stmtStock->execute([
                        ':cantidad' => $i['cantidad'],
                        ':id_producto' => $i['id_producto']
                    ]);

                    // revertir FIFO

                    $stmtPedido->execute([
                        ':cantidad' => $i['cantidad'],
                        ':id_pedido' => $i['id_pedido'],
                        ':id_producto' => $i['id_producto']
                    ]);
                }
            }

            // 4. ACTUALIZAR ESTADO DEL SERVICIO

            $stmt = $this->db->prepare(
                "UPDATE servicios
                SET estado = 'cancelado',
                    total = :total,
                    fecha_terminado = CURRENT_DATE
                WHERE id = :id"
            );

            $stmt->execute([
                ':id' => $idServicio,
                ':total' => $cargoCancelacion
            ]);

            $this->db->commit();
            return true;

        } catch (Throwable $e) {
            $this->db->rollBack();

            // Log interno

            error_log($e->getMessage());

            $this->error = 'Error al cancelar el servicio.';
            return false;
        }
    }

    // OBTENER CLIENTE DEL SERVICIO

    public function getClienteServicio(int $idServicio): array {

        $sql = "SELECT c.nombre, c.apellido, c.email FROM clientes AS c
                    INNER JOIN servicios AS s ON (s.id_cliente = c.id)
                WHERE (s.id = :id)";

        $stmt = $this->db->prepare($sql);

        $stmt->execute([
            ':id' => $idServicio,
        ]);

        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    // OBTENER ESTADO DE SERVICIO

    public function getEstadoServicio(int $idServicio): bool {

        $sql = "SELECT estado FROM servicios
                    WHERE (servicios.id = :id) AND (servicios.estado = 'pendiente') LIMIT 1";

        $stmt = $this->db->prepare($sql);

        $stmt->execute([':id' => $idServicio]);

        return $stmt->fetch() !== false;
    }
}