-- ============================================================================
-- SCHEMA: llm
-- TABLE: conversation_messages
-- DESCRIPTION: Mensajes dentro de conversaciones LLM
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 5 - DDL Implementation Roadmap Q1-2026
-- ============================================================================

-- Enum para rol del mensaje
DO $$ BEGIN
    CREATE TYPE llm.message_role AS ENUM (
        'system',               -- Mensaje de sistema
        'user',                 -- Mensaje del usuario
        'assistant',            -- Respuesta del asistente
        'tool',                 -- Resultado de herramienta
        'function'              -- Llamada a funcion (legacy)
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Enum para estado del mensaje
DO $$ BEGIN
    CREATE TYPE llm.message_status AS ENUM (
        'pending',              -- Pendiente de procesar
        'streaming',            -- Streaming en progreso
        'completed',            -- Completado
        'error',                -- Error
        'cancelled',            -- Cancelado
        'edited'                -- Editado por usuario
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Tabla de Mensajes
CREATE TABLE IF NOT EXISTS llm.conversation_messages (
    -- Identificadores
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    conversation_id UUID NOT NULL REFERENCES llm.conversations(id) ON DELETE CASCADE,
    parent_message_id UUID REFERENCES llm.conversation_messages(id),

    -- Rol y contenido
    role llm.message_role NOT NULL,
    content TEXT NOT NULL,
    status llm.message_status NOT NULL DEFAULT 'completed',

    -- Para mensajes de herramientas
    tool_call_id VARCHAR(100),
    tool_name VARCHAR(100),
    tool_input JSONB,
    tool_output JSONB,

    -- Tokens
    token_count INTEGER DEFAULT 0,
    prompt_tokens INTEGER DEFAULT 0,
    completion_tokens INTEGER DEFAULT 0,

    -- Modelo usado
    model VARCHAR(50),

    -- Tiempos de respuesta
    latency_ms INTEGER,                                 -- Tiempo de respuesta
    time_to_first_token_ms INTEGER,                     -- TTFT para streaming

    -- Contenido estructurado
    attachments JSONB DEFAULT '[]'::JSONB,              -- Archivos adjuntos
    citations JSONB DEFAULT '[]'::JSONB,                -- Citas/referencias
    code_blocks JSONB DEFAULT '[]'::JSONB,              -- Bloques de codigo

    -- Markdown y formato
    is_markdown BOOLEAN NOT NULL DEFAULT TRUE,
    rendered_html TEXT,                                 -- HTML pre-renderizado

    -- Feedback
    thumbs_up BOOLEAN,
    feedback_text TEXT,
    feedback_at TIMESTAMPTZ,

    -- Edicion
    is_edited BOOLEAN NOT NULL DEFAULT FALSE,
    original_content TEXT,
    edited_at TIMESTAMPTZ,

    -- Regeneracion
    is_regenerated BOOLEAN NOT NULL DEFAULT FALSE,
    regeneration_count INTEGER NOT NULL DEFAULT 0,
    previous_version_id UUID,

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

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

    -- Indices para orden
    sequence_number INTEGER NOT NULL DEFAULT 0          -- Orden en la conversacion
);

COMMENT ON TABLE llm.conversation_messages IS
'Mensajes individuales dentro de conversaciones LLM';

COMMENT ON COLUMN llm.conversation_messages.tool_call_id IS
'ID de llamada a herramienta para vincular request/response';

-- Indices
CREATE INDEX IF NOT EXISTS idx_messages_conversation
    ON llm.conversation_messages(conversation_id, sequence_number);

CREATE INDEX IF NOT EXISTS idx_messages_parent
    ON llm.conversation_messages(parent_message_id)
    WHERE parent_message_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_messages_role
    ON llm.conversation_messages(role);

CREATE INDEX IF NOT EXISTS idx_messages_tool
    ON llm.conversation_messages(tool_call_id)
    WHERE tool_call_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_messages_created
    ON llm.conversation_messages(created_at DESC);

CREATE INDEX IF NOT EXISTS idx_messages_feedback
    ON llm.conversation_messages(thumbs_up, feedback_at)
    WHERE thumbs_up IS NOT NULL;

-- Trigger para updated_at
DROP TRIGGER IF EXISTS message_updated_at ON llm.conversation_messages;
CREATE TRIGGER message_updated_at
    BEFORE UPDATE ON llm.conversation_messages
    FOR EACH ROW
    EXECUTE FUNCTION llm.update_llm_timestamp();

-- Trigger para actualizar estadisticas de conversacion
CREATE OR REPLACE FUNCTION llm.update_conversation_stats()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        UPDATE llm.conversations
        SET message_count = message_count + 1,
            token_count_input = token_count_input + COALESCE(NEW.prompt_tokens, 0),
            token_count_output = token_count_output + COALESCE(NEW.completion_tokens, 0),
            total_tokens = total_tokens + COALESCE(NEW.token_count, 0),
            last_message_at = NEW.created_at,
            last_user_message_at = CASE WHEN NEW.role = 'user' THEN NEW.created_at ELSE last_user_message_at END
        WHERE id = NEW.conversation_id;
    ELSIF TG_OP = 'DELETE' THEN
        UPDATE llm.conversations
        SET message_count = message_count - 1,
            token_count_input = token_count_input - COALESCE(OLD.prompt_tokens, 0),
            token_count_output = token_count_output - COALESCE(OLD.completion_tokens, 0),
            total_tokens = total_tokens - COALESCE(OLD.token_count, 0)
        WHERE id = OLD.conversation_id;
    END IF;

    RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS message_stats_update ON llm.conversation_messages;
CREATE TRIGGER message_stats_update
    AFTER INSERT OR DELETE ON llm.conversation_messages
    FOR EACH ROW
    EXECUTE FUNCTION llm.update_conversation_stats();

-- Trigger para asignar sequence_number
CREATE OR REPLACE FUNCTION llm.assign_message_sequence()
RETURNS TRIGGER AS $$
BEGIN
    SELECT COALESCE(MAX(sequence_number), 0) + 1 INTO NEW.sequence_number
    FROM llm.conversation_messages
    WHERE conversation_id = NEW.conversation_id;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS message_sequence ON llm.conversation_messages;
CREATE TRIGGER message_sequence
    BEFORE INSERT ON llm.conversation_messages
    FOR EACH ROW
    WHEN (NEW.sequence_number = 0)
    EXECUTE FUNCTION llm.assign_message_sequence();

-- Funcion para obtener historial de conversacion
CREATE OR REPLACE FUNCTION llm.get_conversation_history(
    p_conversation_id UUID,
    p_limit INTEGER DEFAULT 50
)
RETURNS TABLE (
    role llm.message_role,
    content TEXT,
    tool_name VARCHAR,
    tool_output JSONB,
    created_at TIMESTAMPTZ
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        m.role,
        m.content,
        m.tool_name,
        m.tool_output,
        m.created_at
    FROM llm.conversation_messages m
    WHERE m.conversation_id = p_conversation_id
      AND m.status = 'completed'
    ORDER BY m.sequence_number DESC
    LIMIT p_limit;
END;
$$ LANGUAGE plpgsql;

-- Vista de mensajes con feedback negativo (para mejorar)
CREATE OR REPLACE VIEW llm.v_messages_negative_feedback AS
SELECT
    m.id,
    m.conversation_id,
    c.type AS conversation_type,
    m.role,
    m.content,
    m.model,
    m.feedback_text,
    m.created_at
FROM llm.conversation_messages m
JOIN llm.conversations c ON m.conversation_id = c.id
WHERE m.thumbs_up = FALSE
ORDER BY m.feedback_at DESC;

-- RLS Policies (hereda de conversations via JOIN)
ALTER TABLE llm.conversation_messages ENABLE ROW LEVEL SECURITY;

CREATE POLICY messages_via_conversation ON llm.conversation_messages
    FOR ALL
    USING (
        conversation_id IN (
            SELECT id FROM llm.conversations
            WHERE user_id = current_setting('app.current_user_id', true)::UUID
        )
    );

-- Grants
GRANT SELECT, INSERT, UPDATE, DELETE ON llm.conversation_messages TO trading_app;
GRANT SELECT ON llm.conversation_messages TO trading_readonly;
GRANT SELECT ON llm.v_messages_negative_feedback TO trading_app;
GRANT EXECUTE ON FUNCTION llm.get_conversation_history TO trading_app;
