-- ============================================================================
-- SCHEMA: notifications
-- TABLE: notifications
-- DESCRIPTION: Sistema de notificaciones del sistema
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 1 - DDL Implementation Roadmap Q1-2026
-- ============================================================================

-- Crear schema si no existe
CREATE SCHEMA IF NOT EXISTS notifications;

-- Grant usage
GRANT USAGE ON SCHEMA notifications TO trading_app;
GRANT USAGE ON SCHEMA notifications TO trading_readonly;

-- Enum para tipo de notificacion
DO $$ BEGIN
    CREATE TYPE notifications.notification_type AS ENUM (
        'info',             -- Informativa general
        'success',          -- Accion exitosa
        'warning',          -- Advertencia
        'error',            -- Error
        'alert',            -- Alerta importante
        'trade',            -- Relacionada a trading
        'signal',           -- Senal de trading
        'payment',          -- Pago/transaccion
        'account',          -- Cuenta de usuario
        'security',         -- Seguridad
        'system',           -- Sistema/mantenimiento
        'promotion'         -- Promocional
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Enum para prioridad
DO $$ BEGIN
    CREATE TYPE notifications.notification_priority AS ENUM (
        'low',
        'normal',
        'high',
        'critical'
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Enum para canal de entrega
DO $$ BEGIN
    CREATE TYPE notifications.delivery_channel AS ENUM (
        'in_app',           -- Notificacion en app
        'email',            -- Email
        'push',             -- Push notification
        'sms',              -- SMS
        'webhook'           -- Webhook externo
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Tabla principal de Notificaciones
CREATE TABLE IF NOT EXISTS notifications.notifications (
    -- Identificadores
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
    user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE,

    -- Clasificacion
    type notifications.notification_type NOT NULL DEFAULT 'info',
    priority notifications.notification_priority NOT NULL DEFAULT 'normal',
    category VARCHAR(50),                   -- Subcategoria libre

    -- Contenido
    title VARCHAR(255) NOT NULL,
    message TEXT NOT NULL,
    message_html TEXT,                      -- Version HTML del mensaje

    -- Datos estructurados
    data JSONB DEFAULT '{}'::JSONB,         -- Datos adicionales para la UI
    action_url TEXT,                        -- URL de accion principal
    action_label VARCHAR(100),              -- Texto del boton de accion
    actions JSONB,                          -- Array de acciones multiples

    -- Estado
    is_read BOOLEAN NOT NULL DEFAULT FALSE,
    read_at TIMESTAMPTZ,
    is_archived BOOLEAN NOT NULL DEFAULT FALSE,
    archived_at TIMESTAMPTZ,
    is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
    deleted_at TIMESTAMPTZ,

    -- Entrega multicanal
    channels notifications.delivery_channel[] NOT NULL DEFAULT ARRAY['in_app']::notifications.delivery_channel[],
    delivery_status JSONB DEFAULT '{}'::JSONB,  -- Estado por canal

    -- Tracking de emails
    email_sent BOOLEAN NOT NULL DEFAULT FALSE,
    email_sent_at TIMESTAMPTZ,
    email_opened BOOLEAN NOT NULL DEFAULT FALSE,
    email_opened_at TIMESTAMPTZ,
    email_clicked BOOLEAN NOT NULL DEFAULT FALSE,
    email_clicked_at TIMESTAMPTZ,

    -- Push notifications
    push_sent BOOLEAN NOT NULL DEFAULT FALSE,
    push_sent_at TIMESTAMPTZ,
    push_delivered BOOLEAN NOT NULL DEFAULT FALSE,
    push_delivered_at TIMESTAMPTZ,

    -- Agrupacion
    group_key VARCHAR(100),                 -- Agrupar notificaciones similares
    group_count INTEGER DEFAULT 1,          -- Contador de agrupacion

    -- Programacion
    scheduled_for TIMESTAMPTZ,              -- Envio programado
    expires_at TIMESTAMPTZ,                 -- Expiracion automatica

    -- Origen
    source VARCHAR(50) NOT NULL DEFAULT 'system',  -- 'system', 'user', 'bot', 'webhook'
    source_id VARCHAR(255),                 -- ID del recurso origen
    source_type VARCHAR(50),                -- Tipo de recurso origen

    -- Metadata
    metadata JSONB DEFAULT '{}'::JSONB,

    -- Timestamps
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

COMMENT ON TABLE notifications.notifications IS
'Notificaciones del sistema para usuarios, soporta multicanal';

COMMENT ON COLUMN notifications.notifications.channels IS
'Canales de entrega: in_app, email, push, sms, webhook';

COMMENT ON COLUMN notifications.notifications.group_key IS
'Clave para agrupar notificaciones similares y evitar spam';

-- Indices para queries comunes
CREATE INDEX IF NOT EXISTS idx_notifications_user_unread
    ON notifications.notifications(user_id, created_at DESC)
    WHERE is_read = FALSE AND is_deleted = FALSE;

CREATE INDEX IF NOT EXISTS idx_notifications_user_all
    ON notifications.notifications(user_id, created_at DESC)
    WHERE is_deleted = FALSE;

CREATE INDEX IF NOT EXISTS idx_notifications_tenant
    ON notifications.notifications(tenant_id, created_at DESC);

CREATE INDEX IF NOT EXISTS idx_notifications_type
    ON notifications.notifications(type);

CREATE INDEX IF NOT EXISTS idx_notifications_priority
    ON notifications.notifications(priority)
    WHERE priority IN ('high', 'critical');

CREATE INDEX IF NOT EXISTS idx_notifications_scheduled
    ON notifications.notifications(scheduled_for)
    WHERE scheduled_for IS NOT NULL AND scheduled_for > NOW();

CREATE INDEX IF NOT EXISTS idx_notifications_expires
    ON notifications.notifications(expires_at)
    WHERE expires_at IS NOT NULL AND is_deleted = FALSE;

CREATE INDEX IF NOT EXISTS idx_notifications_group
    ON notifications.notifications(user_id, group_key, created_at DESC)
    WHERE group_key IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_notifications_source
    ON notifications.notifications(source, source_type, source_id);

-- GIN index para busqueda en datos JSONB
CREATE INDEX IF NOT EXISTS idx_notifications_data_gin
    ON notifications.notifications USING GIN (data);

-- Trigger para updated_at
CREATE OR REPLACE FUNCTION notifications.update_notification_timestamp()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS notification_updated_at ON notifications.notifications;
CREATE TRIGGER notification_updated_at
    BEFORE UPDATE ON notifications.notifications
    FOR EACH ROW
    EXECUTE FUNCTION notifications.update_notification_timestamp();

-- Trigger para marcar read_at cuando is_read cambia a true
CREATE OR REPLACE FUNCTION notifications.set_read_timestamp()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.is_read = TRUE AND OLD.is_read = FALSE THEN
        NEW.read_at = NOW();
    END IF;
    IF NEW.is_archived = TRUE AND OLD.is_archived = FALSE THEN
        NEW.archived_at = NOW();
    END IF;
    IF NEW.is_deleted = TRUE AND OLD.is_deleted = FALSE THEN
        NEW.deleted_at = NOW();
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS notification_status_timestamps ON notifications.notifications;
CREATE TRIGGER notification_status_timestamps
    BEFORE UPDATE ON notifications.notifications
    FOR EACH ROW
    EXECUTE FUNCTION notifications.set_read_timestamp();

-- Funcion para crear notificacion
CREATE OR REPLACE FUNCTION notifications.create_notification(
    p_tenant_id UUID,
    p_user_id UUID,
    p_type notifications.notification_type,
    p_title VARCHAR(255),
    p_message TEXT,
    p_priority notifications.notification_priority DEFAULT 'normal',
    p_channels notifications.delivery_channel[] DEFAULT ARRAY['in_app']::notifications.delivery_channel[],
    p_data JSONB DEFAULT '{}'::JSONB,
    p_action_url TEXT DEFAULT NULL,
    p_source VARCHAR(50) DEFAULT 'system',
    p_source_type VARCHAR(50) DEFAULT NULL,
    p_source_id VARCHAR(255) DEFAULT NULL,
    p_group_key VARCHAR(100) DEFAULT NULL,
    p_scheduled_for TIMESTAMPTZ DEFAULT NULL,
    p_expires_at TIMESTAMPTZ DEFAULT NULL
)
RETURNS UUID AS $$
DECLARE
    v_notification_id UUID;
BEGIN
    INSERT INTO notifications.notifications (
        tenant_id, user_id, type, title, message, priority,
        channels, data, action_url, source, source_type, source_id,
        group_key, scheduled_for, expires_at
    ) VALUES (
        p_tenant_id, p_user_id, p_type, p_title, p_message, p_priority,
        p_channels, p_data, p_action_url, p_source, p_source_type, p_source_id,
        p_group_key, p_scheduled_for, p_expires_at
    )
    RETURNING id INTO v_notification_id;

    RETURN v_notification_id;
END;
$$ LANGUAGE plpgsql;

-- Funcion para marcar como leidas por lotes
CREATE OR REPLACE FUNCTION notifications.mark_all_read(
    p_user_id UUID,
    p_before TIMESTAMPTZ DEFAULT NULL
)
RETURNS INTEGER AS $$
DECLARE
    v_count INTEGER;
BEGIN
    UPDATE notifications.notifications
    SET is_read = TRUE,
        read_at = NOW()
    WHERE user_id = p_user_id
      AND is_read = FALSE
      AND is_deleted = FALSE
      AND (p_before IS NULL OR created_at <= p_before);

    GET DIAGNOSTICS v_count = ROW_COUNT;
    RETURN v_count;
END;
$$ LANGUAGE plpgsql;

-- Funcion para limpiar notificaciones expiradas
CREATE OR REPLACE FUNCTION notifications.cleanup_expired()
RETURNS INTEGER AS $$
DECLARE
    v_count INTEGER;
BEGIN
    UPDATE notifications.notifications
    SET is_deleted = TRUE,
        deleted_at = NOW()
    WHERE expires_at IS NOT NULL
      AND expires_at < NOW()
      AND is_deleted = FALSE;

    GET DIAGNOSTICS v_count = ROW_COUNT;
    RETURN v_count;
END;
$$ LANGUAGE plpgsql;

-- Vista de notificaciones no leidas
CREATE OR REPLACE VIEW notifications.v_unread_notifications AS
SELECT
    n.id,
    n.tenant_id,
    n.user_id,
    n.type,
    n.priority,
    n.title,
    n.message,
    n.data,
    n.action_url,
    n.source,
    n.created_at
FROM notifications.notifications n
WHERE n.is_read = FALSE
  AND n.is_deleted = FALSE
  AND (n.expires_at IS NULL OR n.expires_at > NOW())
  AND (n.scheduled_for IS NULL OR n.scheduled_for <= NOW())
ORDER BY
    CASE n.priority
        WHEN 'critical' THEN 1
        WHEN 'high' THEN 2
        WHEN 'normal' THEN 3
        WHEN 'low' THEN 4
    END,
    n.created_at DESC;

-- Vista de conteo por tipo para dashboard
CREATE OR REPLACE VIEW notifications.v_notification_counts AS
SELECT
    user_id,
    COUNT(*) FILTER (WHERE is_read = FALSE) AS unread_count,
    COUNT(*) FILTER (WHERE priority = 'critical' AND is_read = FALSE) AS critical_count,
    COUNT(*) FILTER (WHERE priority = 'high' AND is_read = FALSE) AS high_priority_count,
    COUNT(*) FILTER (WHERE type = 'trade' AND is_read = FALSE) AS trade_count,
    COUNT(*) FILTER (WHERE type = 'signal' AND is_read = FALSE) AS signal_count
FROM notifications.notifications
WHERE is_deleted = FALSE
  AND (expires_at IS NULL OR expires_at > NOW())
GROUP BY user_id;

-- RLS Policy para multi-tenancy
ALTER TABLE notifications.notifications ENABLE ROW LEVEL SECURITY;

CREATE POLICY notifications_tenant_isolation ON notifications.notifications
    FOR ALL
    USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);

-- Los usuarios solo pueden ver sus propias notificaciones
CREATE POLICY notifications_user_isolation ON notifications.notifications
    FOR SELECT
    USING (user_id = current_setting('app.current_user_id', true)::UUID);

-- Grants
GRANT SELECT, INSERT, UPDATE ON notifications.notifications TO trading_app;
GRANT SELECT ON notifications.notifications TO trading_readonly;
GRANT SELECT ON notifications.v_unread_notifications TO trading_app;
GRANT SELECT ON notifications.v_notification_counts TO trading_app;
GRANT EXECUTE ON FUNCTION notifications.create_notification TO trading_app;
GRANT EXECUTE ON FUNCTION notifications.mark_all_read TO trading_app;
GRANT EXECUTE ON FUNCTION notifications.cleanup_expired TO trading_app;
