-- =========================================================================
--  Esquema de base de datos: ALERTAS, REGLAS Y SUSCRIPCIONES A PROCESOS
--  Buscador Multisearch — Entrega 5 (sistema de notificaciones)
-- =========================================================================
--
--  Extiende `03_db_procesos_schema.sql`. Las tablas nuevas referencian
--  las existentes (procesos, eventos_proceso, usuarios). Cargar después
--  del schema base.
--
--  Cargar con: psql -d buscador -f 07_db_alertas_y_reglas.sql
-- =========================================================================


-- =========================================================================
--  EXTENSIÓN: roles de usuario (academia tiene varios)
-- =========================================================================

-- Si la tabla usuarios ya existe (de 03_db_procesos_schema.sql),
-- añadirle el campo rol.
ALTER TABLE usuarios ADD COLUMN IF NOT EXISTS rol VARCHAR(40)
    NOT NULL DEFAULT 'opositor_individual';
    -- Valores: 'academia_direccion', 'academia_contenidos',
    -- 'academia_comercial', 'opositor_individual',
    -- 'opositor_inscrito', 'opositor_admitido', 'opositor_aprobado',
    -- 'admin_sistema'

ALTER TABLE usuarios ADD COLUMN IF NOT EXISTS organizacion VARCHAR(80);
    -- Para academias: identificador de la organización (cgd_formacion,
    -- academia_x, etc.). NULL para usuarios individuales.

CREATE INDEX IF NOT EXISTS idx_usuarios_rol ON usuarios(rol);
CREATE INDEX IF NOT EXISTS idx_usuarios_organizacion ON usuarios(organizacion);


-- =========================================================================
--  TABLA: suscripciones_proceso
--  Usuarios que siguen un proceso concreto (no solo una categoría)
-- =========================================================================

CREATE TABLE suscripciones_proceso (
    id_suscripcion      UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    id_usuario          UUID NOT NULL REFERENCES usuarios(id_usuario)
                            ON DELETE CASCADE,
    id_proceso          VARCHAR(200) NOT NULL REFERENCES procesos(id_proceso)
                            ON DELETE CASCADE,

    -- Etiqueta libre del usuario para este proceso
    -- ("Mi oposición", "Curso 2026 Hacienda", etc.)
    etiqueta            TEXT,

    -- Preferencias por canal para ESTE proceso (sobrescriben las globales)
    email_diario        BOOLEAN NOT NULL DEFAULT TRUE,
    email_inmediato     BOOLEAN NOT NULL DEFAULT TRUE,
    incluir_en_ical     BOOLEAN NOT NULL DEFAULT TRUE,

    -- Qué fases interesan especialmente (NULL = todas)
    fases_interes       INTEGER[],  -- [2, 3, 6] = bases, presentación, examen

    fecha_alta          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    fecha_baja          TIMESTAMPTZ
);

CREATE UNIQUE INDEX idx_suscripciones_proceso_unica
    ON suscripciones_proceso(id_usuario, id_proceso)
    WHERE fecha_baja IS NULL;
CREATE INDEX idx_suscripciones_proceso_usuario
    ON suscripciones_proceso(id_usuario)
    WHERE fecha_baja IS NULL;


-- =========================================================================
--  TABLA: preferencias_notificacion
--  Configuración global por usuario (nivel 1 simple)
-- =========================================================================

CREATE TABLE preferencias_notificacion (
    id_preferencia      UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    id_usuario          UUID NOT NULL REFERENCES usuarios(id_usuario)
                            ON DELETE CASCADE,

    -- Canales activos
    email_diario_activo     BOOLEAN NOT NULL DEFAULT TRUE,
    email_inmediato_activo  BOOLEAN NOT NULL DEFAULT TRUE,
    push_web_activo         BOOLEAN NOT NULL DEFAULT FALSE,
    sms_activo              BOOLEAN NOT NULL DEFAULT FALSE,
    telefono_sms            VARCHAR(20),
    ical_activo             BOOLEAN NOT NULL DEFAULT TRUE,
    ical_url_token          VARCHAR(64),  -- token único para la URL del .ics

    -- Para email inmediato: ¿qué fases?
    inmediato_fase_oep              BOOLEAN NOT NULL DEFAULT FALSE,
    inmediato_fase_bases            BOOLEAN NOT NULL DEFAULT TRUE,
    inmediato_fase_presentacion     BOOLEAN NOT NULL DEFAULT TRUE,
    inmediato_fase_lista_prov       BOOLEAN NOT NULL DEFAULT TRUE,
    inmediato_fase_lista_def        BOOLEAN NOT NULL DEFAULT TRUE,
    inmediato_fase_fecha_examen     BOOLEAN NOT NULL DEFAULT TRUE,
    inmediato_fase_desarrollo       BOOLEAN NOT NULL DEFAULT FALSE,
    inmediato_fase_resultados       BOOLEAN NOT NULL DEFAULT TRUE,
    inmediato_fase_aprobados        BOOLEAN NOT NULL DEFAULT TRUE,
    inmediato_fase_nombramiento     BOOLEAN NOT NULL DEFAULT FALSE,
    inmediato_correccion_errores    BOOLEAN NOT NULL DEFAULT TRUE,
    inmediato_ampliacion_plazo      BOOLEAN NOT NULL DEFAULT TRUE,

    -- Para iCal: ¿qué tipos de fecha?
    ical_fin_plazo                  BOOLEAN NOT NULL DEFAULT TRUE,
    ical_examenes                   BOOLEAN NOT NULL DEFAULT TRUE,
    ical_subsanaciones              BOOLEAN NOT NULL DEFAULT TRUE,
    ical_toma_posesion              BOOLEAN NOT NULL DEFAULT FALSE,

    -- Antispam (anti-burst)
    max_inmediatos_por_hora         SMALLINT NOT NULL DEFAULT 10,
    max_inmediatos_por_dia          SMALLINT NOT NULL DEFAULT 30,

    -- Horario de envío del email diario
    hora_envio_diario               TIME NOT NULL DEFAULT '19:00',
    zona_horaria                    VARCHAR(40) NOT NULL DEFAULT 'Europe/Madrid',

    fecha_actualizacion             TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE UNIQUE INDEX idx_preferencias_usuario
    ON preferencias_notificacion(id_usuario);


-- =========================================================================
--  TABLA: reglas_notificacion
--  Reglas avanzadas (nivel 2) en formato JSONB
-- =========================================================================

CREATE TABLE reglas_notificacion (
    id_regla            UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

    -- A quién aplica la regla
    -- Una de estas tres opciones:
    id_usuario          UUID REFERENCES usuarios(id_usuario)
                            ON DELETE CASCADE,  -- regla personal
    organizacion        VARCHAR(80),            -- regla de organización
    es_regla_sistema    BOOLEAN NOT NULL DEFAULT FALSE,  -- regla del sistema

    -- Configuración
    nombre              TEXT NOT NULL,
    descripcion         TEXT,
    activa              BOOLEAN NOT NULL DEFAULT TRUE,
    prioridad           SMALLINT NOT NULL DEFAULT 100,  -- < número = más prioridad

    -- Condición: estructura JSONB con la lógica
    -- Ejemplo:
    -- {
    --   "fase": "bases",
    --   "administracion": "ayto_madrid",
    --   "cuerpo": "tag",
    --   "porcentaje_cambio_temario_min": 20
    -- }
    condicion           JSONB NOT NULL,

    -- Acción: qué hacer cuando dispara
    -- Ejemplo:
    -- {
    --   "canales": ["email_inmediato", "dashboard_alerta"],
    --   "destinatarios": ["contenidos@academia.es", "direccion@academia.es"],
    --   "asunto_template": "🟢 Nuevas bases {cuerpo} en {administracion}",
    --   "cuerpo_extra": "Revisar temario y actualizar materiales."
    -- }
    accion              JSONB NOT NULL,

    -- Antiabuso
    max_disparos_por_dia    SMALLINT NOT NULL DEFAULT 100,
    [REDACTED]    SMALLINT NOT NULL DEFAULT 0,
    fecha_reset_contador    TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    suspendida_hasta        TIMESTAMPTZ,

    fecha_creacion          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    fecha_actualizacion     TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_reglas_usuario        ON reglas_notificacion(id_usuario)
                                            WHERE id_usuario IS NOT NULL;
CREATE INDEX idx_reglas_organizacion   ON reglas_notificacion(organizacion)
                                            WHERE organizacion IS NOT NULL;
CREATE INDEX idx_reglas_sistema        ON reglas_notificacion(es_regla_sistema)
                                            WHERE es_regla_sistema = TRUE;
CREATE INDEX idx_reglas_activas        ON reglas_notificacion(activa)
                                            WHERE activa = TRUE;
CREATE INDEX idx_reglas_condicion      ON reglas_notificacion USING GIN(condicion);

-- Restricción: cada regla pertenece a uno y solo uno de los tres ámbitos
ALTER TABLE reglas_notificacion ADD CONSTRAINT regla_ambito_unico
    CHECK (
        (id_usuario IS NOT NULL)::int +
        (organizacion IS NOT NULL)::int +
        (es_regla_sistema)::int = 1
    );


-- =========================================================================
--  TABLA: eventos_notificacion
--  Cola unificada de eventos a notificar. Punto único de entrada del
--  motor de reglas. Cada evento detectado por el buscador crea una fila.
-- =========================================================================

CREATE TABLE eventos_notificacion (
    id_evento_notif     UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

    -- Origen del evento
    id_evento_proceso   UUID REFERENCES eventos_proceso(id_evento)
                            ON DELETE CASCADE,
    id_proceso          VARCHAR(200) REFERENCES procesos(id_proceso)
                            ON DELETE CASCADE,

    -- Clasificación
    tipo                VARCHAR(40) NOT NULL,
        -- 'cambio_fase', 'modificador_detectado', 'fecha_proxima',
        -- 'diff_temario_calculado', 'proceso_huerfano'
    fase                VARCHAR(40),
    numero_fase         SMALLINT,
    modificador         VARCHAR(40),  -- 'correccion_errores', 'ampliacion_plazo', ...

    -- Urgencia (heredada de la fase, ajustable por reglas)
    urgencia            VARCHAR(20) NOT NULL DEFAULT 'media',
        -- 'critica', 'alta', 'media', 'baja'

    -- Datos del evento (estructura libre según tipo)
    datos               JSONB,
        -- Ejemplo: {"fecha_fin_plazo": "2026-06-15", "tasa": 25.50, ...}

    -- Estado del procesamiento
    estado              VARCHAR(20) NOT NULL DEFAULT 'pendiente',
        -- 'pendiente', 'procesado', 'descartado'

    fecha_creacion      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    fecha_procesamiento TIMESTAMPTZ
);

CREATE INDEX idx_evnotif_estado ON eventos_notificacion(estado)
                                     WHERE estado = 'pendiente';
CREATE INDEX idx_evnotif_proceso ON eventos_notificacion(id_proceso);
CREATE INDEX idx_evnotif_urgencia ON eventos_notificacion(urgencia, fecha_creacion);


-- =========================================================================
--  TABLA: notificaciones_enviadas
--  Histórico de cada notificación que se ha disparado a un usuario
-- =========================================================================

CREATE TABLE notificaciones_enviadas (
    id_notificacion     UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

    id_evento_notif     UUID NOT NULL REFERENCES eventos_notificacion(id_evento_notif)
                            ON DELETE CASCADE,
    id_usuario          UUID NOT NULL REFERENCES usuarios(id_usuario)
                            ON DELETE CASCADE,
    id_regla            UUID REFERENCES reglas_notificacion(id_regla)
                            ON DELETE SET NULL,

    canal               VARCHAR(40) NOT NULL,
        -- 'email_diario', 'email_inmediato', 'push_web', 'sms',
        -- 'dashboard_alerta', 'ical'

    estado              VARCHAR(20) NOT NULL DEFAULT 'pendiente',
        -- 'pendiente', 'enviada', 'fallida', 'silenciada_antispam',
        -- 'silenciada_usuario'

    asunto              TEXT,
    cuerpo_html         TEXT,
    cuerpo_texto        TEXT,

    fecha_envio         TIMESTAMPTZ,
    fecha_apertura      TIMESTAMPTZ,
    fecha_clic          TIMESTAMPTZ,
    error               TEXT,

    fecha_creacion      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_notif_evento     ON notificaciones_enviadas(id_evento_notif);
CREATE INDEX idx_notif_usuario    ON notificaciones_enviadas(id_usuario,
                                                              fecha_creacion DESC);
CREATE INDEX idx_notif_estado     ON notificaciones_enviadas(estado)
                                       WHERE estado = 'pendiente';
CREATE INDEX idx_notif_canal      ON notificaciones_enviadas(canal,
                                                              fecha_creacion DESC);


-- =========================================================================
--  TABLA: silenciados
--  Lista de "no enviar más esto a este usuario" — para botón "silenciar"
-- =========================================================================

CREATE TABLE silenciados (
    id_silenciado       UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    id_usuario          UUID NOT NULL REFERENCES usuarios(id_usuario)
                            ON DELETE CASCADE,

    -- Qué silenciar: combinación de criterios (cualquiera que matchee
    -- silencia la notificación)
    id_proceso          VARCHAR(200) REFERENCES procesos(id_proceso)
                            ON DELETE CASCADE,
    fase                VARCHAR(40),
    modificador         VARCHAR(40),
    categoria           VARCHAR(80),
    canal               VARCHAR(40),

    motivo              TEXT,
    fecha_alta          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    fecha_caducidad     TIMESTAMPTZ  -- NULL = silencio permanente
);

CREATE INDEX idx_silenciados_usuario ON silenciados(id_usuario);


-- =========================================================================
--  VISTA: eventos_pendientes_inmediato
--  Eventos esperando ser procesados por el job de email inmediato
-- =========================================================================

CREATE OR REPLACE VIEW eventos_pendientes_inmediato AS
SELECT
    en.id_evento_notif,
    en.id_proceso,
    en.tipo,
    en.fase,
    en.numero_fase,
    en.modificador,
    en.urgencia,
    en.datos,
    en.fecha_creacion,
    p.administracion_codigo,
    p.administracion_nombre,
    p.cuerpo_codigo,
    p.cuerpo_nombre,
    p.categoria_tematica
FROM eventos_notificacion en
JOIN procesos p ON p.id_proceso = en.id_proceso
WHERE en.estado = 'pendiente'
  AND en.urgencia IN ('critica', 'alta')
ORDER BY
  CASE en.urgencia
      WHEN 'critica' THEN 1
      WHEN 'alta' THEN 2
      ELSE 3
  END,
  en.fecha_creacion;


-- =========================================================================
--  VISTA: procesos_seguidos_por_usuario
--  Para cada usuario, qué procesos sigue (vía suscripción directa o
--  vía suscripción a la categoría temática)
-- =========================================================================

CREATE OR REPLACE VIEW procesos_seguidos_por_usuario AS
SELECT
    u.id_usuario,
    u.email,
    p.id_proceso,
    p.administracion_nombre,
    p.cuerpo_nombre,
    p.categoria_tematica,
    p.estado,
    -- ¿Por qué la sigue?
    CASE
        WHEN sp.id_suscripcion IS NOT NULL THEN 'directa'
        WHEN s.id_suscripcion IS NOT NULL THEN 'por_categoria'
    END AS tipo_suscripcion
FROM usuarios u
JOIN procesos p ON TRUE  -- ya filtramos abajo
LEFT JOIN suscripciones_proceso sp
    ON sp.id_usuario = u.id_usuario
   AND sp.id_proceso = p.id_proceso
   AND sp.fecha_baja IS NULL
LEFT JOIN suscripciones s
    ON s.id_usuario = u.id_usuario
   AND s.categoria = p.categoria_tematica
   AND s.activa = TRUE
WHERE u.estado = 'activo'
  AND p.estado != 'archivado'
  AND (sp.id_suscripcion IS NOT NULL OR s.id_suscripcion IS NOT NULL);


-- =========================================================================
--  FUNCIÓN: evaluar_destinatarios
--  Para un evento de notificación, devuelve la lista de usuarios que
--  deben recibirlo según las reglas, silencios y antispam.
-- =========================================================================

CREATE OR REPLACE FUNCTION evaluar_destinatarios(p_id_evento_notif UUID)
RETURNS TABLE (
    id_usuario          UUID,
    canal               VARCHAR(40),
    id_regla_origen     UUID
) AS $$
BEGIN
    -- Esta función materializa la lógica del motor de reglas. La
    -- implementación completa es compleja (involucra evaluar las
    -- condiciones JSONB de cada regla). Aquí se da el esqueleto.

    RETURN QUERY
    -- 1. Suscriptores directos al proceso
    SELECT
        sp.id_usuario,
        CASE
            WHEN sp.email_inmediato THEN 'email_inmediato'
            ELSE 'email_diario'
        END::VARCHAR(40) AS canal,
        NULL::UUID AS id_regla_origen
    FROM eventos_notificacion en
    JOIN procesos p ON p.id_proceso = en.id_proceso
    JOIN suscripciones_proceso sp ON sp.id_proceso = p.id_proceso
                                  AND sp.fecha_baja IS NULL
    WHERE en.id_evento_notif = p_id_evento_notif

    UNION

    -- 2. Suscriptores a la categoría temática del proceso
    SELECT
        s.id_usuario,
        'email_diario'::VARCHAR(40) AS canal,
        NULL::UUID AS id_regla_origen
    FROM eventos_notificacion en
    JOIN procesos p ON p.id_proceso = en.id_proceso
    JOIN suscripciones s ON s.categoria = p.categoria_tematica
                         AND s.activa = TRUE
    WHERE en.id_evento_notif = p_id_evento_notif
      -- Pero NO si ya tienen suscripción directa
      AND NOT EXISTS (
          SELECT 1 FROM suscripciones_proceso sp
          WHERE sp.id_usuario = s.id_usuario
            AND sp.id_proceso = p.id_proceso
            AND sp.fecha_baja IS NULL
      );

    -- TODO: 3. Aplicar reglas_notificacion (evaluación de condicion JSONB)
    -- TODO: 4. Excluir silenciados (tabla silenciados)
    -- TODO: 5. Aplicar antispam (max_inmediatos_por_hora/dia)
END;
$$ LANGUAGE plpgsql;


-- =========================================================================
--  COMENTARIOS DE USO
-- =========================================================================
--
--  Flujo completo de notificación:
--
--  1. El buscador detecta un evento (nuevo hit) y lo persiste en
--     eventos_proceso (tabla del schema base).
--  2. Un trigger (o el propio código de la aplicación) crea una entrada
--     en eventos_notificacion con el tipo, fase y urgencia derivados.
--  3. Un job nocturno procesa los eventos para email_diario.
--     Un job cada 5 minutos procesa los críticos/altos para email_inmediato.
--  4. Para cada evento, llama a evaluar_destinatarios() y crea una fila
--     en notificaciones_enviadas por cada combinación usuario × canal.
--  5. El sender de emails toma las notificaciones pendientes, renderiza
--     la plantilla correspondiente y las envía.
--
--  Para el calendario .ics:
--  - Cada usuario con ical_activo=TRUE tiene una URL única
--    (https://buscador.example/ical/{ical_url_token}.ics) que sirve
--    un calendario dinámico generado a partir de las fechas_clave de
--    los procesos a los que está suscrito.
--
--  Para el dashboard:
--  - Lee directamente las tablas procesos, eventos_proceso,
--    fechas_clave, eventos_notificacion (cola de alertas pendientes
--    de mostrar al usuario al entrar).
