<?php
/**
 * Domicilio — Gestión de servicios a domicilio del laboratorio.
 *
 * Flujo de estados:
 *   programado → confirmado → en_camino → en_domicilio → completado
 *                           └→ cancelado / reprogramado
 */

require_once __DIR__ . '/../../classes/Database.php';
require_once __DIR__ . '/ActividadAdmin.php';

class Domicilio {

    const ESTADOS = [
        'programado',
        'confirmado',
        'en_camino',
        'en_domicilio',
        'completado',
        'cancelado',
        'reprogramado',
    ];

    private Database $db;
    private ActividadAdmin $log;

    public function __construct() {
        $this->db  = Database::getInstance();
        $this->log = new ActividadAdmin();
    }

    // ─────────────────────────────────────────────────────────────────────────
    // CRUD
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Lista de domicilios con filtros.
     *
     * @param array $filtros  fecha, estado, enfermera_id, paciente_id
     */
    public function listar(array $filtros = [], int $pagina = 1, int $porPagina = 30): array {
        $wheres = [];
        $params = [];

        if (!empty($filtros['fecha'])) {
            $wheres[] = 'd.fecha_programada = ?';
            $params[] = $filtros['fecha'];
        }
        if (!empty($filtros['desde'])) {
            $wheres[] = 'd.fecha_programada >= ?';
            $params[] = $filtros['desde'];
        }
        if (!empty($filtros['hasta'])) {
            $wheres[] = 'd.fecha_programada <= ?';
            $params[] = $filtros['hasta'];
        }
        if (!empty($filtros['estado'])) {
            $wheres[] = 'd.estado = ?';
            $params[] = $filtros['estado'];
        }
        if (!empty($filtros['enfermera_id'])) {
            $wheres[] = 'a.enfermera_id = ?';
            $params[] = $filtros['enfermera_id'];
        }
        if (!empty($filtros['paciente_id'])) {
            $wheres[] = 'd.paciente_id = ?';
            $params[] = $filtros['paciente_id'];
        }

        $where  = $wheres ? 'WHERE ' . implode(' AND ', $wheres) : '';
        $offset = ($pagina - 1) * $porPagina;

        $total = $this->db->fetch(
            "SELECT COUNT(*) AS n
             FROM   lab_domicilios d
             LEFT   JOIN lab_asignaciones a ON a.domicilio_id = d.id
             $where",
            $params
        )['n'] ?? 0;

        $rows = $this->db->fetchAll("
            SELECT
                d.*,
                p.nombre_completo   AS paciente_nombre,
                p.telefono          AS paciente_telefono,
                e.nombre_completo   AS enfermera_nombre,
                e.telefono          AS enfermera_telefono,
                a.enfermera_id      AS enfermera_lab_id,
                a.id                AS asignacion_id,
                a.estado            AS asignacion_estado,
                CASE
                    WHEN d.hora_en_camino IS NULL OR d.hora_llegada IS NULL THEN NULL
                    ELSE TIMESTAMPDIFF(
                        MINUTE,
                        CONCAT(d.fecha_programada, ' ', d.hora_en_camino),
                        CONCAT(d.fecha_programada, ' ', d.hora_llegada)
                    )
                END AS tiempo_recorrido_min
            FROM  lab_domicilios  d
            JOIN  lab_pacientes   p ON p.id = d.paciente_id
            LEFT  JOIN lab_asignaciones  a ON a.domicilio_id = d.id
            LEFT  JOIN lab_enfermeras    e ON e.id = a.enfermera_id
            $where
            ORDER BY d.fecha_programada ASC, d.hora_programada ASC
            LIMIT ? OFFSET ?
        ", array_merge($params, [$porPagina, $offset]));

        return [
            'data'       => $rows,
            'total'      => (int)$total,
            'pagina'     => $pagina,
            'por_pagina' => $porPagina,
            'paginas'    => (int)ceil($total / $porPagina),
        ];
    }

    /**
     * Domicilio por ID con todos los datos relacionados.
     */
    public function obtener(int $id): ?array {
        $dom = $this->db->fetch("
            SELECT
                d.*,
                p.nombre_completo   AS paciente_nombre,
                p.telefono          AS paciente_telefono,
                p.email             AS paciente_email,
                p.fecha_nacimiento  AS paciente_fecha_nacimiento,
                p.tipo_documento    AS paciente_tipo_documento,
                p.numero_documento  AS paciente_numero_documento,
                p.direccion         AS paciente_direccion,
                o.examenes_solicitados,
                o.indicaciones     AS indicaciones_orden,
                o.local_file       AS orden_local_file,
                e.nombre_completo  AS enfermera_nombre,
                e.telefono         AS enfermera_telefono,
                a.enfermera_id     AS enfermera_lab_id,
                a.id               AS asignacion_id,
                a.estado           AS asignacion_estado,
                adm.full_name      AS creado_por_nombre,
                CASE
                    WHEN d.hora_en_camino IS NULL OR d.hora_llegada IS NULL THEN NULL
                    ELSE TIMESTAMPDIFF(
                        MINUTE,
                        CONCAT(d.fecha_programada, ' ', d.hora_en_camino),
                        CONCAT(d.fecha_programada, ' ', d.hora_llegada)
                    )
                END AS tiempo_recorrido_min
            FROM  lab_domicilios  d
            JOIN  lab_pacientes   p   ON p.id = d.paciente_id
            LEFT  JOIN lab_ordenes_medicas o ON o.id = d.orden_id
            LEFT  JOIN lab_asignaciones    a ON a.domicilio_id = d.id
            LEFT  JOIN lab_enfermeras      e ON e.id = a.enfermera_id
            LEFT  JOIN admin_users         adm ON adm.id = d.creado_por
            WHERE d.id = ?
        ", [$id]);
        if (!$dom) return null;

        // Todos los archivos de órdenes vinculados a este domicilio
        $archivos = $this->db->fetchAll(
            "SELECT id AS orden_id, local_file
             FROM lab_ordenes_medicas
             WHERE domicilio_id = ? AND local_file IS NOT NULL
             ORDER BY id ASC",
            [$id]
        );
        // Fallback para domicilios creados antes de la migración (usan orden_id FK)
        if (empty($archivos) && !empty($dom['orden_local_file'])) {
            $archivos = [['orden_id' => $dom['orden_id'], 'local_file' => $dom['orden_local_file']]];
        }
        $dom['archivos_ordenes'] = $archivos;

        // Servicios extras registrados por el enfermero
        $dom['servicios_extra'] = $this->db->fetchAll(
            "SELECT id, descripcion, tipo, notas, requiere_pago, valor, created_at
             FROM lab_servicios_extra
             WHERE domicilio_id = ?
             ORDER BY created_at ASC",
            [$id]
        );

        // Pagos parciales por método de pago
        $dom['pagos_detalle'] = $this->db->fetchAll(
            "SELECT modo, monto, notas FROM lab_domicilio_pagos WHERE domicilio_id = ? ORDER BY id ASC",
            [$id]
        );

        // Vínculo: domicilio origen (este fue reprogramado desde otro)
        if (!empty($dom['domicilio_origen_id'])) {
            $dom['origen_info'] = $this->db->fetch(
                "SELECT id, fecha_programada, hora_programada, estado FROM lab_domicilios WHERE id = ?",
                [(int)$dom['domicilio_origen_id']]
            );
        }

        // Vínculo: domicilio(s) reprogramado(s) desde este
        $dom['reprogramaciones'] = $this->db->fetchAll(
            "SELECT id, fecha_programada, hora_programada, estado FROM lab_domicilios WHERE domicilio_origen_id = ? ORDER BY id ASC",
            [$id]
        );

        return $dom;
    }

    /**
     * Crear nuevo domicilio.
     */
    public function crear(array $datos, ?int $adminId = null): int {
        $campos              = $this->filtrarCampos($datos);
        $campos['creado_por'] = $adminId;

        // Actualizar orden médica si aplica
        if (!empty($campos['orden_id'])) {
            $this->db->update(
                'lab_ordenes_medicas',
                ['estado' => 'en_domicilio'],
                'id = ?', [$campos['orden_id']]
            );
        }

        $id = (int)$this->db->insert('lab_domicilios', $campos);

        $this->log->registrar($adminId, 'domicilios', 'crear', $id, $campos);
        return $id;
    }

    /**
     * Actualizar domicilio.
     */
    public function actualizar(int $id, array $datos, ?int $adminId = null): bool {
        $campos = $this->filtrarCampos($datos);
        $ok     = $this->db->update('lab_domicilios', $campos, 'id = ?', [$id]);
        $this->log->registrar($adminId, 'domicilios', 'editar', $id, $campos);
        return $ok > 0;
    }

    /**
     * Cambiar estado del domicilio.
     */
    public function cambiarEstado(
        int $id,
        string $nuevoEstado,
        ?int $adminId,
        array $extras = []
    ): bool {
        if (!in_array($nuevoEstado, self::ESTADOS)) {
            throw new InvalidArgumentException("Estado inválido: $nuevoEstado");
        }

        $campos = array_merge(['estado' => $nuevoEstado], $extras);
        $ok     = $this->db->update('lab_domicilios', $campos, 'id = ?', [$id]);
        $this->log->registrar($adminId, 'domicilios', $nuevoEstado, $id, $extras);
        return $ok > 0;
    }

    /**
     * Reprogramar un domicilio:
     *   1. Marca el original como 'reprogramado' con fecha_reprogramada.
     *   2. Clona el domicilio para la nueva fecha/hora.
     *   3. Vincula el clon con domicilio_origen_id.
     *
     * @return int  ID del nuevo domicilio creado.
     */
    public function reprogramar(
        int    $idOrigen,
        string $nuevaFecha,
        string $nuevaHora = '',
        string $motivo    = '',
        ?int   $adminId   = null
    ): int {
        // 1. Obtener datos del original
        $origen = $this->db->fetch(
            'SELECT * FROM lab_domicilios WHERE id = ?', [$idOrigen]
        );
        if (!$origen) {
            throw new \RuntimeException('Domicilio no encontrado');
        }

        // 2. Marcar original como reprogramado
        $extrasOrigen = [
            'fecha_reprogramada' => $nuevaFecha,
        ];
        if ($motivo) $extrasOrigen['motivo_cancelacion'] = $motivo;
        $this->cambiarEstado($idOrigen, 'reprogramado', $adminId, $extrasOrigen);

        // 3. Clonar campos relevantes para el nuevo domicilio
        $campos = $this->filtrarCampos($origen);
        // Resetear campos operativos del original
        unset(
            $campos['estado'], $campos['motivo_cancelacion'],
            $campos['fecha_reprogramada'], $campos['hora_llegada'],
            $campos['hora_salida'], $campos['observaciones'],
            $campos['muestras_tomadas'], $campos['pago_estado'],
            $campos['pago_modo'], $campos['pago_monto'],
            $campos['pago_fecha'], $campos['pago_notas'],
            $campos['pago_registrado_por'], $campos['temperatura_inicio'],
            $campos['temperatura_salida'], $campos['domicilio_origen_id']
        );
        $campos['fecha_programada']   = $nuevaFecha;
        $campos['hora_programada']    = $nuevaHora ?: null;
        $campos['estado']             = 'programado';
        $campos['domicilio_origen_id']= $idOrigen;
        $campos['creado_por']         = $adminId;

        $nuevoId = (int)$this->db->insert('lab_domicilios', $campos);
        $this->log->registrar($adminId, 'domicilios', 'reprogramar', $nuevoId, [
            'origen_id'   => $idOrigen,
            'nueva_fecha' => $nuevaFecha,
        ]);
        return $nuevoId;
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Agenda del día
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Todos los domicilios de hoy (o la fecha indicada).
     */
    public function agendaDia(string $fecha = ''): array {
        $fecha = $fecha ?: date('Y-m-d');
        return $this->listar(['fecha' => $fecha], 1, 200)['data'];
    }

    /**
     * Domicilios sin enfermera asignada.
     */
    public function sinAsignar(string $fecha = ''): array {
        $fecha  = $fecha ?: date('Y-m-d');
        return $this->db->fetchAll("
            SELECT
                d.*,
                p.nombre_completo AS paciente_nombre
            FROM  lab_domicilios d
            JOIN  lab_pacientes  p ON p.id = d.paciente_id
            LEFT  JOIN lab_asignaciones a ON a.domicilio_id = d.id
            WHERE d.fecha_programada = ?
              AND a.id IS NULL
              AND d.estado NOT IN ('cancelado','completado')
            ORDER BY d.hora_programada ASC
        ", [$fecha]);
    }

    /**
     * Estadísticas: conteo de domicilios por estado hoy.
     */
    public function estadisticasHoy(): array {
        $rows = $this->db->fetchAll("
            SELECT estado, COUNT(*) AS total
            FROM   lab_domicilios
            WHERE  fecha_programada = CURDATE()
            GROUP  BY estado
        ");
        $result = array_fill_keys(self::ESTADOS, 0);
        foreach ($rows as $row) {
            $result[$row['estado']] = (int)$row['total'];
        }
        return $result;
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Helpers
    // ─────────────────────────────────────────────────────────────────────────

    private function filtrarCampos(array $datos): array {
        $permitidos = [
            'orden_id', 'paciente_id', 'numero_orden', 'direccion', 'ciudad', 'barrio',
            'indicaciones_dir', 'fecha_programada', 'hora_programada',
            'tipo_servicio', 'tipo_cliente', 'examenes_solicitados',
            'estado', 'motivo_cancelacion',
            'fecha_reprogramada', 'hora_llegada', 'hora_salida',
            'observaciones', 'muestras_tomadas', 'notas_admin',
            'seguro_nombre', 'autorizacion', 'copago_laboratorio',
            'valor_domicilio', 'valor_copago',
            'domicilio_origen_id',
            // pago
            'pago_estado', 'pago_modo', 'pago_monto', 'pago_fecha',
            'pago_notas', 'pago_registrado_por',
        ];
        $campos = array_intersect_key($datos, array_flip($permitidos));

        // Campos FK que deben ser NULL cuando vienen vacíos o cero
        foreach (['orden_id', 'paciente_id'] as $fk) {
            if (array_key_exists($fk, $campos) && ($campos[$fk] === '' || $campos[$fk] === '0' || $campos[$fk] === 0)) {
                $campos[$fk] = null;
            }
        }

        // Normalizar campos de fecha/hora vacíos a NULL para evitar error SQL (MySQL strict)
        foreach (['fecha_programada', 'fecha_reprogramada', 'hora_programada', 'hora_llegada', 'hora_salida', 'pago_fecha'] as $dt) {
            if (array_key_exists($dt, $campos) && trim((string)$campos[$dt]) === '') {
                $campos[$dt] = null;
            }
        }

        // Normalizar campos numéricos opcionales vacíos a NULL para evitar "Incorrect decimal value: ''"
        foreach (['valor_domicilio', 'valor_copago', 'copago_laboratorio', 'pago_monto'] as $num) {
            if (array_key_exists($num, $campos) && trim((string)$campos[$num]) === '') {
                $campos[$num] = null;
            }
        }

        return $campos;
    }
}
