<?php

// models/model_agenda.php

// Incluimos el archivo que contiene la clase para la conexión a la base de datos (Database).
// Es un servicio que se encarga de gestionar la conexión.
require_once '../services/pdo_connection.php';

// Definimos la clase AgendaModel, que actúa como el "Modelo" en el patrón MVC.
// Su única responsabilidad es interactuar con la base de datos para la tabla 'agenda'.
class AgendaModel {
    // Declaramos una propiedad privada para almacenar el objeto de conexión PDO.
    private $conn;

    // El constructor se ejecuta cuando se crea una nueva instancia de la clase.
    // Aquí inicializamos la conexión a la base de datos.
    public function __construct() {
        // Creamos una nueva instancia de la clase Database.
        $database = new Database();
        // Obtenemos el objeto de conexión PDO de esa instancia.
        $this->conn = $database->getConnection();
    }

    /**
     * Obtiene todos los productos de evento que están activos.
     * @return array Un array de productos o un array vacío en caso de error o si no hay resultados.
     */
    public function obtenerProductosEvento() {
        // Preparamos la consulta SQL para seleccionar todos los productos activos.
        $sql = "SELECT id_producto_evento, titulo, descripcion, precio, foto_url FROM producto_evento WHERE activo = 1";
        try {
            // Preparamos la declaración SQL para su ejecución.
            $stmt = $this->conn->prepare($sql);
            // Ejecutamos la consulta.
            $stmt->execute();
            // Devolvemos todos los resultados como un array asociativo.
            return $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (PDOException $e) {
            // Capturamos cualquier error de la base de datos y lo registramos en el log del servidor.
            error_log("Error en obtenerProductosEvento: " . $e->getMessage());
            // Devolvemos un array vacío para indicar que no se pudieron obtener los datos.
            return [];
        }
    }

    /**
     * Obtiene el precio de un producto específico.
     * Este es un método privado porque solo se usa internamente en esta clase.
     * @param int $id_producto El ID del producto.
     * @return float El precio del producto o 0 en caso de error o si no se encuentra.
     */
    private function obtenerPrecioProducto($id_producto) {
        // Consulta SQL para obtener el precio de un producto por su ID.
        $sql = "SELECT precio FROM producto_evento WHERE id_producto_evento = :id";
        try {
            $stmt = $this->conn->prepare($sql);
            // Vinculamos el parámetro :id con el valor del ID del producto.
            $stmt->bindParam(':id', $id_producto, PDO::PARAM_INT);
            $stmt->execute();
            $result = $stmt->fetch(PDO::FETCH_ASSOC);
            // Devolvemos el precio si se encuentra, de lo contrario 0.
            return $result ? $result['precio'] : 0;
        } catch (PDOException $e) {
            error_log("Error al obtener precio del producto: " . $e->getMessage());
            return 0;
        }
    }

    /**
     * Obtiene un registro de agenda completo, incluyendo el título del producto.
     * @param int $id_agenda El ID del registro de agenda.
     * @return array|null El registro de agenda o null en caso de error o si no se encuentra.
     */
    public function obtenerAgendaPorIdConDetalles($id_agenda) {
        // Consulta SQL que une las tablas 'agenda' y 'producto_evento' para obtener
        // los detalles de la agenda junto con el título del producto.
        $sql = "SELECT a.*, pe.titulo AS titulo_producto
                FROM agenda a
                JOIN producto_evento pe ON a.id_producto_evento = pe.id_producto_evento
                WHERE a.id_agenda = :id_agenda LIMIT 1";

        try {
            $stmt = $this->conn->prepare($sql);
            $stmt->bindParam(':id_agenda', $id_agenda, PDO::PARAM_INT);
            $stmt->execute();
            return $stmt->fetch(PDO::FETCH_ASSOC);
        } catch (PDOException $e) {
            error_log("Error al obtener detalles de la agenda: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Verifica si existe un conflicto de reservación para una fecha y hora dadas.
     * Esto evita que se registren múltiples eventos en la misma franja horaria.
     * @param string $fecha La fecha del evento.
     * @param string $hora La hora del evento.
     * @return bool Devuelve true si hay un conflicto (ya existe una reserva), false si está disponible.
     */
    public function verificarConflictoReservacion($fecha, $hora) {
        // Usamos COUNT(*) para contar cuántos registros existen con esa fecha y hora.
        $sql = "SELECT COUNT(*) FROM agenda WHERE fecha_evento = :fecha AND hora_evento = :hora";
        try {
            $stmt = $this->conn->prepare($sql);
            $stmt->bindParam(':fecha', $fecha, PDO::PARAM_STR);
            $stmt->bindParam(':hora', $hora, PDO::PARAM_STR);
            $stmt->execute();
            
            // Si el conteo es mayor a 0, significa que el horario ya está ocupado.
            return $stmt->fetchColumn() > 0;
        } catch (PDOException $e) {
            error_log("Error al verificar conflicto de reservación: " . $e->getMessage());
            // En caso de error, es más seguro asumir que hay un conflicto para evitar doble reserva.
            return true;
        }
    }

    /**
     * Guarda un nuevo registro de agenda y un historial asociado.
     * Esta operación se realiza dentro de una transacción para asegurar la consistencia.
     * @param array $data Los datos del pedido.
     * @return int|false El ID del nuevo registro de agenda si tiene éxito, o false si falla.
     * @throws Exception Si hay un error de base de datos que provoca un rollback.
     */
    public function guardarAgenda($data) {
        // Iniciamos una transacción para que todas las operaciones se realicen o ninguna.
        $this->conn->beginTransaction();

        try {
            // Obtenemos el precio total del producto del evento.
            $precio_total = $this->obtenerPrecioProducto($data['id_producto_evento']);
            // Calculamos el monto restante a pagar.
            $falta_por_pagar = $precio_total - $data['anticipo'];
            
            // Convertimos explícitamente los valores a números de punto flotante (decimales)
            // para evitar errores de tipo de dato en la base de datos.
            $anticipo_numerico = floatval($data['anticipo']);
            $falta_por_pagar_numerico = floatval($falta_por_pagar);

            // 1. Inserción en la tabla 'agenda'
            $sql_agenda = "INSERT INTO agenda (
                id_producto_evento, fecha_evento, hora_evento, anticipo,
                falta_por_pagar, estado, nombre_cliente, telefono, direccion_texto,
                referencia, notas, concepto, metodo_pago
            ) VALUES (
                :id_producto_evento, :fecha_evento, :hora_evento, :anticipo,
                :falta_por_pagar, 'RESERVADO', :nombre_cliente, :telefono, :direccion_texto,
                :referencia, :notas, :concepto, :metodo_pago
            )";

            $stmt_agenda = $this->conn->prepare($sql_agenda);
            
            // Vinculamos todos los parámetros con sus respectivos valores.
            $stmt_agenda->bindParam(':id_producto_evento', $data['id_producto_evento'], PDO::PARAM_INT);
            $stmt_agenda->bindParam(':fecha_evento', $data['fecha_evento']);
            $stmt_agenda->bindParam(':hora_evento', $data['hora_evento']);
            $stmt_agenda->bindParam(':anticipo', $anticipo_numerico);
            $stmt_agenda->bindParam(':falta_por_pagar', $falta_por_pagar_numerico);
            $stmt_agenda->bindParam(':nombre_cliente', $data['nombre_cliente']);
            $stmt_agenda->bindParam(':telefono', $data['telefono']);
            $stmt_agenda->bindParam(':direccion_texto', $data['direccion_texto']);
            $stmt_agenda->bindParam(':referencia', $data['referencia']);
            $stmt_agenda->bindParam(':notas', $data['notas']);
            $stmt_agenda->bindParam(':concepto', $data['concepto']);
            $stmt_agenda->bindParam(':metodo_pago', $data['metodo_pago']);
            
            if (!$stmt_agenda->execute()) {
                // Si la ejecución falla, lanzamos una excepción para que se capture y haga un rollback.
                throw new Exception("Error al insertar en la tabla 'agenda'.");
            }
            
            // Obtenemos el ID del registro recién insertado.
            $id_agenda = $this->conn->lastInsertId();

            // 2. Inserción en la tabla 'historial_agenda'
            // Registramos el ID de la agenda para fines de historial.
            $sql_historial = "INSERT INTO historial_agenda (id_agenda) VALUES (:id_agenda)";
            $stmt_historial = $this->conn->prepare($sql_historial);
            $stmt_historial->bindParam(':id_agenda', $id_agenda, PDO::PARAM_INT);
            
            if (!$stmt_historial->execute()) {
                throw new Exception("Error al insertar en la tabla 'historial_agenda'.");
            }
            
            // Si todo ha ido bien, confirmamos la transacción para guardar los cambios en la DB.
            $this->conn->commit();
            return $id_agenda;

        } catch (Exception $e) {
            // En caso de cualquier error, revertimos la transacción para deshacer los cambios.
            $this->conn->rollBack();
            // Relanzamos la excepción para que pueda ser manejada por la capa superior (el controlador).
            throw new Exception("Error al guardar la reservación: " . $e->getMessage());
        }
    }

    /**
     * Registra un pago parcial o total y actualiza los saldos.
     * @param int|string $id_agenda El ID de la agenda a actualizar.
     * @param float $monto_pago El monto del pago a registrar.
     * @return array Resultado de la operación con 'success', 'message', y los nuevos saldos.
     */
    public function registrarAnticipo($id_agenda, $monto_pago) {
        try {
            // Iniciamos una transacción.
            $this->conn->beginTransaction();

            // Seleccionamos los saldos actuales y bloqueamos la fila con 'FOR UPDATE'
            // para evitar que otro proceso la modifique al mismo tiempo (condición de carrera).
            $stmt = $this->conn->prepare("SELECT anticipo, falta_por_pagar FROM agenda WHERE id_agenda = ? FOR UPDATE");
            $stmt->execute([$id_agenda]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            // Verificamos si se encontró el registro.
            if (!$row) {
                $this->conn->rollBack();
                return ['success' => false, 'message' => 'Registro de agenda no encontrado.'];
            }

            // Calculamos el nuevo anticipo y la nueva deuda.
            $nuevo_anticipo = $row['anticipo'] + $monto_pago;
            $nueva_falta = $row['falta_por_pagar'] - $monto_pago;
            
            // Nos aseguramos de que el monto pendiente no sea negativo.
            if ($nueva_falta < 0) {
                $nueva_falta = 0;
            }

            // Preparamos la consulta para actualizar los campos.
            $stmt = $this->conn->prepare("UPDATE agenda SET anticipo = ?, falta_por_pagar = ? WHERE id_agenda = ?");
            $stmt->execute([$nuevo_anticipo, $nueva_falta, $id_agenda]);

            // Si todo fue exitoso, confirmamos la transacción.
            $this->conn->commit();

            // Devolvemos una respuesta de éxito con los nuevos saldos actualizados.
            return [
                'success' => true,
                'message' => 'Anticipo registrado correctamente',
                'nuevo_anticipo' => number_format($nuevo_anticipo, 2, '.', ''),
                'nueva_falta_por_pagar' => number_format($nueva_falta, 2, '.', '')
            ];

        } catch (PDOException $e) {
            // Si hubo un error en la base de datos, revertimos la transacción.
            if ($this->conn->inTransaction()) {
                $this->conn->rollBack();
            }
            error_log("Error al registrar anticipo: " . $e->getMessage());
            return ['success' => false, 'message' => 'Error en la base de datos: ' . $e->getMessage()];
        }
    }

    /**
     * Actualiza dinámicamente el estado, el saldo pendiente y el ID de pago de un registro de agenda.
     * Se construye la consulta según los parámetros no nulos.
     * @param int $id_agenda El ID del registro de agenda.
     * @param string|null $nuevo_estado El nuevo estado.
     * @param float|null $falta_por_pagar El nuevo saldo pendiente.
     * @param string|null $payment_id El ID del pago.
     * @return bool True si la actualización fue exitosa, false si falló.
     */
    public function actualizarEstadoPagoAgenda($id_agenda, $nuevo_estado, $falta_por_pagar, $payment_id) {
        $query_parts = [];
        $params = [];

        // Comprobamos cada parámetro y si no es nulo, lo añadimos a la consulta.
        if (!is_null($nuevo_estado)) {
            $query_parts[] = "estado = ?";
            $params[] = $nuevo_estado;
        }
        if (!is_null($falta_por_pagar)) {
            $query_parts[] = "falta_por_pagar = ?";
            $params[] = $falta_por_pagar;
        }
        if (!is_null($payment_id)) {
            $query_parts[] = "payment_id = ?";
            $params[] = $payment_id;
        }

        // Si no hay nada que actualizar, retornamos true.
        if (empty($query_parts)) {
            return true;
        }

        // Construimos la consulta SQL final uniendo las partes.
        $sql = "UPDATE agenda SET " . implode(', ', $query_parts) . " WHERE id_agenda = ?";
        // Añadimos el ID de la agenda al final de los parámetros.
        $params[] = $id_agenda;

        try {
            $stmt = $this->conn->prepare($sql);
            // Ejecutamos la consulta con los parámetros dinámicamente.
            return $stmt->execute($params);
        } catch (PDOException $e) {
            error_log("Error al actualizar estado de agenda: " . $e->getMessage());
            return false;
        }
    }
}