# backend/data_manager.py
"""
Capa de acceso a datos del módulo LEADS.
- PostgreSQL (Chatwoot): leads de pauta (consolidado en UNA sola consulta).
- SQL Server (Académico): matrículas y cursos.
- GitHub: mapa de campañas (frases -> programa/sede), config editable.
"""
import os
import json
import requests
from datetime import datetime
from dotenv import load_dotenv

load_dotenv()


class DataManager:
    def __init__(self):
        # PostgreSQL (Chatwoot)
        self.pg_host = os.getenv("PG_HOST", "191.98.134.81")
        self.pg_db   = os.getenv("PG_DATABASE", "chatwoot_production")
        self.pg_user = os.getenv("PG_USER", "postgres")
        self.pg_pass = os.getenv("PG_PASSWORD", "")
        self.pg_port = os.getenv("PG_PORT", "5432")
        # SQL Server (Académico)
        self.sql_server = os.getenv("SQL_SERVER", "191.98.134.80")
        self.sql_db     = os.getenv("SQL_DATABASE", "BDUS_CK000040_0001")
        self.sql_user   = os.getenv("SQL_USERNAME", "")
        self.sql_pass   = os.getenv("SQL_PASSWORD", "")
        self.sql_driver = os.getenv("SQL_DRIVER", "{SQL Server}")
        # GitHub
        base = os.getenv("GITHUB_BASE", "")
        self.github_campanias_url = f"{base}/campanias.json" if base else ""
        self.campanias = []
        self._cargar_campanias()

    # ── Conexiones ──────────────────────────────────────────────
    def pg_conn(self):
        import psycopg2
        return psycopg2.connect(
            host=self.pg_host, dbname=self.pg_db, user=self.pg_user,
            password=self.pg_pass, port=self.pg_port,
        )

    def sql_conn(self):
        import pyodbc
        conn_str = (
            f"DRIVER={self.sql_driver};SERVER={self.sql_server};"
            f"DATABASE={self.sql_db};UID={self.sql_user};PWD={self.sql_pass}"
        )
        return pyodbc.connect(conn_str)

    # ── Config de campañas (GitHub, opcional) ───────────────────
    def _cargar_campanias(self):
        if not self.github_campanias_url:
            self.campanias = []
            return
        try:
            r = requests.get(self.github_campanias_url, timeout=5)
            r.raise_for_status()
            data = r.json()
            self.campanias = data.get("campanias", []) if isinstance(data, dict) else data
        except Exception:
            self.campanias = []

    # ════════════════════════════════════════════════════════════
    # CHATWOOT — UN SOLO QUERY CONSOLIDADO DE LEADS
    # Reemplaza los 4 queries del PBI (procesados, asignados,
    # contactados, etiquetas) por uno solo. El resto se calcula
    # en Python (Cantidad_Veces, Ultima_Etiqueta, Estado/Objeción).
    # ════════════════════════════════════════════════════════════
    def traer_leads_chatwoot(self):
        """Leads de PAUTA (igual que el PBI): un mensaje cuenta como lead solo si su
        texto coincide con una FRASE de campaña y está dentro del rango de fechas de
        esa campaña. El mapa de campañas está incrustado (como en el Power BI)."""
        sql = """
        SELECT DISTINCT ON (m.id)
            REGEXP_REPLACE(c.phone_number, '[^0-9]', '', 'g') AS telefono,
            u.name        AS asesor,
            (m.created_at - INTERVAL '5 hours') AS fecha_creada,
            cv.cached_label_list                AS etiquetas,
            map.cargo    AS programa,
            map.sede     AS sede,
            map.codigo   AS codigo,
            map.origen   AS origen
        FROM messages m
        JOIN conversations cv ON m.conversation_id = cv.id
        JOIN contacts c       ON cv.contact_id = c.id
        LEFT JOIN users u     ON cv.assignee_id = u.id
        JOIN (VALUES
            ('📚 Me interesa el', 'TERC', '200', 'Arequipa', 'Domingo', 'Pauta_wsp', '2025-10-01', '2025-12-31'),
            ('📚 Me interesa el', 'TEAC', '227', 'Lima',     'Domingo', 'Pauta_wsp', '2026-01-15', '2026-03-30'),
            ('Hola! 🚨', 'TEAC', '191', 'Piura',    'Sábado',  'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 💯 Me interesa', 'TERC', '193', 'Lima', 'Domingo', 'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 💼', 'TEAC', '195', 'Trujillo', 'Domingo', 'Pauta_wsp', '2025-09-01', '2026-01-31'),
            ('Hola! 💸', 'TEAC', '197', 'Lima',     'Sábado',  'Pauta_wsp', '2025-09-01', '2026-02-28'),
            ('Hola! ⚡', 'TEAC', '198', 'Trujillo', 'Domingo', 'Pauta_wsp', '2025-09-01', '2026-01-31'),
            ('Hola! 🦺', 'TERC', '199', 'Piura',    'Domingo', 'Pauta_wsp', '2025-09-01', '2026-02-28'),
            ('Hola! 🍾', 'TEAC', '201', 'Arequipa', 'Sábado',  'Pauta_wsp', '2025-09-01', '2026-02-28'),
            ('¡Hola! 📚', 'TERC', '200', 'Arequipa', 'Domingo',  'Pauta_wsp', '2025-09-01', '2026-01-31'),
            ('Hola! 🥶', 'TEAC', '202', 'Lima',     'Sábado',  'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 🎯', 'TEAC', '203', 'Lima',     'Domingo', 'Pauta_wsp', '2025-09-01', '2026-02-28'),
            ('Hola! 🌞', 'TERC', '204', 'Lima',     'Sábado',  'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 🗿', 'TERC', '205', 'Lima',     'Domingo', 'Pauta_wsp', '2025-09-01', '2026-02-28'),
            ('Hola! 😎', 'TEAC', '206', 'Piura',    'Domingo', 'Pauta_wsp', '2025-09-01', '2026-04-01'),
            ('Hola! 🎓', 'TEAC', '207', 'Arequipa', 'Domingo', 'Pauta_wsp', '2025-09-01', '2026-01-31'),
            ('Hola! 👀', 'TEAC', '208', 'Trujillo', 'Sábado',  'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 🎮', 'TERC', '209', 'Piura',    'Sábado',  'Pauta_wsp', '2025-09-01', '2026-04-30'),
            ('Hola! 👾', 'TERC', '210', 'Arequipa', 'Sábado',  'Pauta_wsp', '2025-09-01', '2026-02-25'),
            ('Hola! 🕹️', 'TERC', '211', 'Trujillo', 'Domingo', 'Pauta_wsp', '2025-09-01', '2026-04-01'),
            ('Hola! 🚀', 'TEAC', '212', 'Lima',     '-',       'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 🛸', 'TERC', '213', 'Lima',     '-',       'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 🦾', 'Amoniaco', '63a', 'Lima', '-',       'Pauta_wsp', '2025-09-01', '2026-04-15'),
            ('Hola! 🛠️', 'Diseño Chillers', '64a', 'Lima', '-', 'Pauta_wsp', '2025-09-01', '2026-04-30'),
            ('¡Hola! 🥽', 'VRF', '65a', 'Lima',      '-',       'Pauta_wsp', '2025-09-01', '2026-02-28'),
            ('¡Hola! ❄️ Estoy interesado Seminario', 'Cámaras', '66a', 'Lima', '-', 'Pauta_wsp', '2026-02-01', '2026-05-28'),
            ('¡Hola! 🤩', 'Diseño de Sistemas', '67a', 'Lima', '-', 'Pauta_wsp', '2026-02-01', '2026-12-31'),
            ('¡Hola! 🙌🏼', 'Metrado y Costeo', '68a', 'Lima', '-', 'Pauta_wsp', '2026-02-01', '2026-12-31'),
            ('¡Hola! 🥽', 'VRF', '69a', 'Lima',      '-',       'Pauta_wsp', '2026-03-01', '2026-06-30'),
            (' 🧊 Más información del Seminario', 'VRF', '71a', 'Arequipa', '-', 'Pauta_wsp', '2026-03-01', '2026-06-30'),
            (' 📐 Más información del Seminario', 'VRF', '72a', 'Trujillo', '-', 'Pauta_wsp', '2026-03-01', '2026-06-30'),
            (' 👾 Más información del Seminario en Instalación', 'VRF', '73a', 'Piura', '-', 'Pauta_wsp', '2026-03-01', '2026-06-30'),
            (' 🖥️ Estoy interesado en sus Seminarios', 'Seminarios', '74a', 'Lima', '-', 'Pauta_wsp', '2026-04-01', '2026-07-30'),
            (' 🙋 Deseo más info. del Diplomado ', 'Diplomado REF', '75a', 'Lima', '-', 'Pauta_wsp', '2026-04-01', '2026-07-30'),
            ('💸 Deseo más info. del Seminario', 'CAD', '76a', 'Lima', '-', 'Pauta_wsp', '2026-04-01', '2026-07-30'),
            ('🦾 Estoy interesado Instalación de Sistemas', 'VRF', '77a', 'Lima', '-', 'Pauta_wsp', '2026-04-16', '2026-07-30'),
            ('🙀 Estoy interesado Seminario', 'SUPER.OBRAS', '78a', 'Lima', '-', 'Pauta_wsp', '2026-05-15', '2026-07-30'),
            ('🛠️ más info del Seminario Diseño de Chillers', 'Diseño', '79a', 'Lima', '-', 'Pauta_wsp', '2026-05-15', '2026-07-30'),
            ('👾Estoy interesado en el Seminario Refrigeración', 'CO2', '80a', 'Lima', '-', 'Pauta_wsp', '2026-05-15', '2026-07-30'),
            ('Hola! 👉', 'TEAC', '214', 'Lima',     'Sábado',  'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 🤝', 'TEAC', '215', 'Lima',     'Domingo', 'Pauta_wsp', '2025-12-01', '2026-03-15'),
            ('🆕 Me interesa', 'TERC', '217', 'Lima', 'Domingo', 'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 🌬️', 'TEAC', '218', 'Lima',     'Sábado',  'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 🌍', 'TERC', '219', 'Lima',     'Sábado',  'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 🎟️', 'TEAC', '220', 'Lima',     'Sábado',  'Pauta_wsp', '2025-09-01', '2026-03-31'),
            ('Hola! 🌈', 'TERC', '221', 'Lima',     'Sábado',  'Pauta_wsp', '2025-09-01', '2026-04-15'),
            ('¡Hola! ❄️ Me interesa el programa', 'TEAC', '222', 'Lima', 'Domingo', 'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('Hola! 🔝', 'TERC', '223', 'Lima',     'Domingo', 'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('🔋 Me interesa', 'TEAC', '224', 'Lima',    'Sábado',  'Pauta_wsp', '2025-09-01', '2026-04-15'),
            ('¡Hola! 🌡️', 'TEAC', '225', 'Lima',    'Domingo', 'Pauta_wsp', '2025-09-01', '2026-12-31'),
            ('¡Hola! ☃️', 'TERC', '226', 'Lima',    'Sábado', 'Pauta_wsp', '2026-01-01', '2026-04-15'),
            ('¡Hola! 📚', 'TERC', '227', 'Lima',    'Semipresencial', 'Pauta_wsp', '2026-02-01', '2026-05-10'),
            ('¡Hola! 🗺️', 'TEAC', '228', 'Arequipa','Semipresencial', 'Pauta_wsp', '2026-02-01', '2026-05-05'),
            ('¡Hola! 🌤️', 'TEAC', '229', 'Piura',   'Semipresencial', 'Pauta_wsp', '2026-02-01', '2026-05-30'),
            ('¡Hola! ⚡', 'TEAC', '230', 'Trujillo','Semipresencial', 'Pauta_wsp', '2026-02-01', '2026-05-07'),
            ('¡Hola! 🎤', 'TERC', '231', 'Lima',    'Domingo',        'Pauta_wsp', '2026-02-01', '2026-05-30'),
            ('¡Hola! 👾 Me interesa el programa', 'TEAC', '232', 'Lima', 'Domingo', 'Pauta_wsp', '2026-02-26', '2026-05-30'),
            ('¡Hola! 🍨', 'TEAC', '233', 'Lima',    'Sábado',        'Pauta_wsp', '2026-02-01', '2026-05-30'),
            ('¡Hola! 🐶 Me interesa', 'TEAC', '234', 'Piura',    'Sábado',        'Pauta_wsp', '2026-03-01', '2026-05-30'),
            ('¡Hola! 💸 Me interesa', 'TERC', '235', 'Arequipa', 'Semipresencial', 'Pauta_wsp', '2026-03-01', '2026-05-30'),
            ('¡Hola! 🗿 Me interesa', 'TERC', '236', 'Trujillo', 'Sábado',        'Pauta_wsp', '2026-03-01', '2026-05-30'),
            ('¡Hola! 🦺 Me interesa', 'TEAC', '237', 'Lima',    'Sábado',        'Pauta_wsp', '2026-03-01', '2026-05-30'),
            ('¡Hola! 🍾 Me interesa', 'TEAC', '238', 'Lima',    'Domingo',        'Pauta_wsp', '2026-03-01', '2026-05-30'),
            ('💡 deseo más info del programa', 'TERC', '239', 'Lima', 'Semipresencial', 'Pauta_wsp', '2026-03-01', '2026-05-30'),
            ('¡Hola! 🎯 Me interesa el programa', 'TERC', '240', 'Lima', 'Domingo', 'Pauta_wsp', '2026-04-01', '2026-05-30'),
            ('¡Hola! 🤝 Me interesa el programa', 'TERC', '241', 'Lima', 'Sábado', 'Pauta_wsp', '2026-04-01', '2026-05-30'),
            ('🪐 Me interesa el programa', 'TERC', '242', 'Piura',    'Domingo',        'Pauta_wsp', '2026-04-01', '2026-05-30'),
            ('🎓 Me interesa el programa', 'TEAC', '243', 'Arequipa', 'Domingo',        'Pauta_wsp', '2026-04-01', '2026-06-30'),
            ('💼 Me interesa el programa ', 'TEAC', '244', 'Trujillo', 'Domingo',        'Pauta_wsp', '2026-04-01', '2026-06-30'),
            ('☃️ Me interesa el programa', 'TEAC', '245', 'Arequipa', 'Sabado',        'Pauta_wsp', '2026-04-16', '2026-07-30'),
            ('🔋 Me interesa el programa', 'TERC', '246', 'Arequipa', 'Sabado',        'Pauta_wsp', '2026-04-16', '2026-07-30'),
            ('😎 Me interesa el programa', 'TEAC', '247', 'Arequipa', 'Domingo',        'Pauta_wsp', '2026-04-16', '2026-07-30'),
            ('🌈Me interesa el programa', 'TEAC', '248', 'Lima',    'Sabado',        'Pauta_wsp', '2026-04-16', '2026-07-30'),
            ('⭐ Me interesa el programa', 'TEAC', '249', 'Lima',    'Domingo',        'Pauta_wsp', '2026-05-01', '2026-07-30'),
            ('🕵️ Me interesa el programa', 'TEAC', '250', 'Piura',  'Domingo',        'Pauta_wsp', '2026-05-01', '2026-07-30'),
            ('📈 Me interesa el programa', 'TEAC', '251', 'Trujillo', 'Domingo',        'Pauta_wsp', '2026-05-01', '2026-07-30'),
            ('🗺️ Me interesa el programa', 'TEAC', '252', 'Piura',  'Sabado',        'Pauta_wsp', '2026-05-06', '2026-07-30'),
            ('🎮 Me interesa el programa', 'TEAC', '253', 'Trujillo', 'Sabado',        'Pauta_wsp', '2026-05-01', '2026-07-30'),
            ('🏞️ Me interesa el programa', 'TERC', '254', 'Arequipa', 'Domingo',        'Pauta_wsp', '2026-05-01', '2026-12-31'),
            ('⚙️ Me interesa el programa', 'TERC', '255', 'Piura',  'Domingo',        'Pauta_wsp', '2026-05-01', '2026-12-31'),
            ('⚡ Me interesa el programa', 'TERC', '256', 'Trujillo', 'Domingo',        'Pauta_wsp', '2026-05-08', '2026-12-31'),
            ('🎟️ Me interesa el programa', 'TERC', '257', 'Trujillo', 'Sabado',        'Pauta_wsp', '2026-05-01', '2026-12-31'),
            ('🕑 Me interesa el', 'TERC', '258', 'Piura',    'Sabado',        'Pauta_wsp', '2026-05-01', '2026-12-31'),
            ('📚 Me interesa el programa', 'TEAC', '259', 'Lima',    'Semipresencial',        'Pauta_wsp', '2026-05-11', '2026-12-31'),
            ('🤗 Me interesa el programa', 'TEAC', '260', 'Lima',    'Semipresencial',        'Pauta_wsp', '2026-05-11', '2026-12-31'),
            ('🕹️ Me interesa el programa', 'TERC', '261', 'Lima',  'Domingo',        'Pauta_wsp', '2026-05-11', '2026-12-31'),
            ('🐶 Me interesa el programa', 'TEAC', '262', 'Piura',    'Sabado',        'Pauta_wsp', '2026-05-11', '2026-08-30'),
            ('🍨 Me interesa el programa', 'TERC', '263', 'Arequipa', 'Sabado',        'Pauta_wsp', '2026-05-15', '2026-08-30'),
            ('⛱️ Me interesa el programa de Aire Acondicionado', 'TEAC', '264', 'Lima', 'Sabado', 'Pauta_wsp', '2026-05-15', '2026-08-30'),
            ('🌤️ Me interesa el programa', 'TEAC', '265', 'Arequipa', 'Sabado',        'Pauta_wsp', '2026-05-15', '2026-08-30'),
            ('⛱️ Me interesa el programa de Refrigeración comercial', 'TERC', '266', 'Lima', 'Sabado', 'Pauta_wsp', '2026-05-15', '2026-08-30'),
            ('¡Hola! Vengo de su web, quiero saber', 'TEAC', '0', 'Lima', '-', 'Web_Whatsapp', '2025-11-01', '2026-12-31')
        ) AS map(frase_busqueda, cargo, codigo, sede, dia, origen, fecha_inicio, fecha_fin)
            ON m.content LIKE '%' || map.frase_busqueda || '%'
            AND m.created_at >= CAST(map.fecha_inicio AS TIMESTAMP)
            AND m.created_at <= CAST(map.fecha_fin AS TIMESTAMP) + INTERVAL '1 day'
        WHERE m.sender_type = 'Contact'
        ORDER BY m.id ASC, m.created_at ASC
        """
        conn = self.pg_conn()
        cur = conn.cursor()
        cur.execute(sql)
        cols = [d[0] for d in cur.description]
        filas = [dict(zip(cols, row)) for row in cur.fetchall()]
        conn.close()
        return filas

    # ════════════════════════════════════════════════════════════
    # SQL SERVER — CURSOS (igual que el PBI: Fact_SQL_Base_Cursos)
    # ════════════════════════════════════════════════════════════
    def traer_cursos(self):
        sql = """
        SELECT
            rp.num_indice,
            rp.dsc_det_programa,
            p.dsc_programa,
            rp.cod_frecuencia,
            rp.fch_inicio,
            rp.cod_estado,
            (SELECT COUNT(*) FROM sgeca_matricula m
                WHERE m.cod_periodo = rp.cod_detalle AND m.num_indice = rp.num_indice
                  AND m.cod_estado NOT IN ('ANU')) AS Inscritos_Totales,
            (SELECT COUNT(*) FROM sgeca_matricula m
                WHERE m.cod_periodo = rp.cod_detalle AND m.num_indice = rp.num_indice
                  AND m.cod_estado = 'RET') AS Inscritos_Retirados,
            (SELECT COUNT(*) FROM sgeca_matricula m
                WHERE m.cod_periodo = rp.cod_detalle AND m.num_indice = rp.num_indice
                  AND m.cod_estado NOT IN ('ANU','RET','SUS')) AS Inscritos_Activos
        FROM sgede_RP_programa rp
        INNER JOIN sgeca_programa p ON rp.cod_programa = p.cod_programa
        WHERE YEAR(rp.fch_inicio) IN (2025, 2026)
        ORDER BY rp.fch_inicio ASC
        """
        conn = self.sql_conn()
        cur = conn.cursor()
        cur.execute(sql)
        cols = [d[0] for d in cur.description]
        filas = [dict(zip(cols, row)) for row in cur.fetchall()]
        conn.close()
        return filas

    # ════════════════════════════════════════════════════════════
    # SQL SERVER — MATRÍCULAS (Fact_SQL_Base_Matriculas, resumido)
    # ════════════════════════════════════════════════════════════
    def traer_matriculas(self):
        sql = """
        SELECT
            sgeca_matricula.num_matricula,
            sgeca_matricula.num_indice,
            sgeca_matricula.fch_matricula,
            sgeca_matricula.cod_estado AS estado_matricula,
            REPLACE(sgema_alumno.dsc_telefono_1,' ','') AS dsc_telefono_1,
            sgeca_programa.dsc_programa,
            sgede_RP_programa.dsc_det_programa AS dsc_promocion,
            ISNULL((SELECT sgede_cronograma_matricula.imp_saldo FROM sgede_cronograma_matricula
                WHERE sgede_cronograma_matricula.cod_localidad = sgeca_matricula.cod_localidad
                  AND sgede_cronograma_matricula.num_matricula = sgeca_matricula.num_matricula
                  AND sgede_cronograma_matricula.num_refinanciamiento = 1
                  AND sgede_cronograma_matricula.num_cuota = 0), 0) AS imp_saldo_matricula,
            ISNULL((SELECT sgede_cronograma_matricula.imp_saldo FROM sgede_cronograma_matricula
                WHERE sgede_cronograma_matricula.cod_localidad = sgeca_matricula.cod_localidad
                  AND sgede_cronograma_matricula.num_matricula = sgeca_matricula.num_matricula
                  AND sgede_cronograma_matricula.num_refinanciamiento = 1
                  AND sgede_cronograma_matricula.num_cuota = 1), 0) AS imp_saldo_cuota1
        FROM sgeca_matricula
        INNER JOIN sgeca_programa ON sgeca_matricula.cod_programa = sgeca_programa.cod_programa
        LEFT JOIN sgede_RP_programa ON sgeca_matricula.cod_periodo = sgede_RP_programa.cod_detalle
            AND sgeca_matricula.cod_programa = sgede_RP_programa.cod_programa
            AND sgeca_matricula.num_indice = sgede_RP_programa.num_indice
        INNER JOIN sgema_alumno ON sgeca_matricula.cod_alumno = sgema_alumno.cod_alumno
        WHERE sgeca_matricula.cod_localidad LIKE 'SCENT'
          AND sgeca_matricula.fch_matricula BETWEEN '01-11-2024 00:00:00.000' AND '31-12-2026 23:59:00.000'
          AND sgeca_matricula.cod_estado <> 'ANU'
        """
        conn = self.sql_conn()
        cur = conn.cursor()
        cur.execute(sql)
        cols = [d[0] for d in cur.description]
        filas = [dict(zip(cols, row)) for row in cur.fetchall()]
        conn.close()
        return filas
# --- fin data_manager ---
