-- ============================================================================
-- SCHEMA: llm
-- TABLE: llm_usage_limits
-- DESCRIPTION: Limites de uso del agente LLM por usuario/plan
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 5 - DDL Implementation Roadmap Q1-2026
-- ============================================================================

-- Enum para periodo de reset
DO $$ BEGIN
    CREATE TYPE llm.reset_period AS ENUM (
        'hourly',
        'daily',
        'weekly',
        'monthly',
        'never'                 -- Sin reset (lifetime limit)
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Tabla de Limites de Uso
CREATE TABLE IF NOT EXISTS llm.llm_usage_limits (
    -- 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,

    -- Plan VIP asociado
    vip_plan_id UUID,                                   -- FK a vip.plans si existe
    vip_level VARCHAR(20),                              -- 'free', 'basic', 'pro', 'enterprise'

    -- Limites de mensajes
    messages_limit INTEGER NOT NULL DEFAULT 100,        -- Mensajes por periodo
    messages_used INTEGER NOT NULL DEFAULT 0,
    messages_reset_period llm.reset_period NOT NULL DEFAULT 'daily',
    messages_reset_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '1 day',

    -- Limites de tokens
    tokens_limit INTEGER NOT NULL DEFAULT 100000,       -- Tokens por periodo
    tokens_used INTEGER NOT NULL DEFAULT 0,
    tokens_reset_period llm.reset_period NOT NULL DEFAULT 'monthly',
    tokens_reset_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '1 month',

    -- Limites de herramientas
    tools_limit INTEGER NOT NULL DEFAULT 50,            -- Llamadas a herramientas por periodo
    tools_used INTEGER NOT NULL DEFAULT 0,
    tools_reset_period llm.reset_period NOT NULL DEFAULT 'daily',
    tools_reset_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '1 day',

    -- Limites de conversaciones
    conversations_limit INTEGER,                        -- Conversaciones activas max (NULL = unlimited)
    conversations_active INTEGER NOT NULL DEFAULT 0,

    -- Modelos permitidos
    allowed_models llm.llm_model[] DEFAULT ARRAY['gpt-4o-mini']::llm.llm_model[],
    default_model llm.llm_model NOT NULL DEFAULT 'gpt-4o-mini',

    -- Herramientas permitidas
    allowed_tools VARCHAR(100)[],                       -- NULL = todas
    blocked_tools VARCHAR(100)[],                       -- Herramientas bloqueadas

    -- Features
    can_use_vision BOOLEAN NOT NULL DEFAULT FALSE,
    can_use_voice BOOLEAN NOT NULL DEFAULT FALSE,
    can_share_conversations BOOLEAN NOT NULL DEFAULT TRUE,
    can_export_conversations BOOLEAN NOT NULL DEFAULT TRUE,
    can_use_code_interpreter BOOLEAN NOT NULL DEFAULT FALSE,

    -- Rate limiting
    requests_per_minute INTEGER DEFAULT 10,
    requests_per_hour INTEGER DEFAULT 100,
    current_minute_requests INTEGER NOT NULL DEFAULT 0,
    current_hour_requests INTEGER NOT NULL DEFAULT 0,
    minute_reset_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '1 minute',
    hour_reset_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '1 hour',

    -- Costo
    monthly_budget_usd DECIMAL(10, 2),                  -- Presupuesto mensual
    monthly_spent_usd DECIMAL(10, 6) NOT NULL DEFAULT 0,
    budget_alert_threshold DECIMAL(5, 2) DEFAULT 80,    -- % para alerta
    budget_alert_sent BOOLEAN NOT NULL DEFAULT FALSE,

    -- Overage (exceso)
    allow_overage BOOLEAN NOT NULL DEFAULT FALSE,
    overage_rate_multiplier DECIMAL(5, 2) DEFAULT 1.5,  -- Multiplicador de costo en overage
    overage_limit_percent DECIMAL(5, 2) DEFAULT 20,     -- % max de overage permitido

    -- Estado
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    is_suspended BOOLEAN NOT NULL DEFAULT FALSE,
    suspension_reason TEXT,
    suspended_at TIMESTAMPTZ,

    -- Notificaciones
    notify_at_percent INTEGER[] DEFAULT ARRAY[50, 80, 100],
    last_notification_percent INTEGER,

    -- Metadata
    custom_limits JSONB DEFAULT '{}'::JSONB,            -- Limites personalizados
    metadata JSONB DEFAULT '{}'::JSONB,

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

    -- Constraints
    CONSTRAINT usage_limits_user_unique UNIQUE (user_id),
    CONSTRAINT usage_limits_valid_percents CHECK (
        budget_alert_threshold BETWEEN 0 AND 100
        AND overage_limit_percent BETWEEN 0 AND 100
    )
);

COMMENT ON TABLE llm.llm_usage_limits IS
'Limites de uso del agente LLM por usuario, vinculado a planes VIP';

COMMENT ON COLUMN llm.llm_usage_limits.allow_overage IS
'Si TRUE, permite uso adicional con costo extra cuando se alcanza el limite';

-- Indices
CREATE INDEX IF NOT EXISTS idx_usage_limits_tenant
    ON llm.llm_usage_limits(tenant_id);

CREATE INDEX IF NOT EXISTS idx_usage_limits_user
    ON llm.llm_usage_limits(user_id);

CREATE INDEX IF NOT EXISTS idx_usage_limits_vip
    ON llm.llm_usage_limits(vip_level);

CREATE INDEX IF NOT EXISTS idx_usage_limits_reset
    ON llm.llm_usage_limits(messages_reset_at, tokens_reset_at);

CREATE INDEX IF NOT EXISTS idx_usage_limits_suspended
    ON llm.llm_usage_limits(is_suspended)
    WHERE is_suspended = TRUE;

CREATE INDEX IF NOT EXISTS idx_usage_limits_budget_alert
    ON llm.llm_usage_limits(monthly_budget_usd, monthly_spent_usd)
    WHERE monthly_budget_usd IS NOT NULL;

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

-- Funcion para verificar y resetear limites
CREATE OR REPLACE FUNCTION llm.check_and_reset_limits(p_user_id UUID)
RETURNS llm.llm_usage_limits AS $$
DECLARE
    v_limits llm.llm_usage_limits;
BEGIN
    SELECT * INTO v_limits FROM llm.llm_usage_limits WHERE user_id = p_user_id FOR UPDATE;

    IF NOT FOUND THEN
        RETURN NULL;
    END IF;

    -- Reset mensajes si es necesario
    IF v_limits.messages_reset_at <= NOW() THEN
        UPDATE llm.llm_usage_limits
        SET messages_used = 0,
            messages_reset_at = NOW() + (
                CASE messages_reset_period
                    WHEN 'hourly' THEN INTERVAL '1 hour'
                    WHEN 'daily' THEN INTERVAL '1 day'
                    WHEN 'weekly' THEN INTERVAL '1 week'
                    WHEN 'monthly' THEN INTERVAL '1 month'
                    ELSE INTERVAL '100 years'
                END
            )
        WHERE user_id = p_user_id;
    END IF;

    -- Reset tokens si es necesario
    IF v_limits.tokens_reset_at <= NOW() THEN
        UPDATE llm.llm_usage_limits
        SET tokens_used = 0,
            tokens_reset_at = NOW() + (
                CASE tokens_reset_period
                    WHEN 'hourly' THEN INTERVAL '1 hour'
                    WHEN 'daily' THEN INTERVAL '1 day'
                    WHEN 'weekly' THEN INTERVAL '1 week'
                    WHEN 'monthly' THEN INTERVAL '1 month'
                    ELSE INTERVAL '100 years'
                END
            )
        WHERE user_id = p_user_id;
    END IF;

    -- Reset herramientas si es necesario
    IF v_limits.tools_reset_at <= NOW() THEN
        UPDATE llm.llm_usage_limits
        SET tools_used = 0,
            tools_reset_at = NOW() + (
                CASE tools_reset_period
                    WHEN 'hourly' THEN INTERVAL '1 hour'
                    WHEN 'daily' THEN INTERVAL '1 day'
                    WHEN 'weekly' THEN INTERVAL '1 week'
                    WHEN 'monthly' THEN INTERVAL '1 month'
                    ELSE INTERVAL '100 years'
                END
            )
        WHERE user_id = p_user_id;
    END IF;

    -- Reset rate limits
    IF v_limits.minute_reset_at <= NOW() THEN
        UPDATE llm.llm_usage_limits
        SET current_minute_requests = 0,
            minute_reset_at = NOW() + INTERVAL '1 minute'
        WHERE user_id = p_user_id;
    END IF;

    IF v_limits.hour_reset_at <= NOW() THEN
        UPDATE llm.llm_usage_limits
        SET current_hour_requests = 0,
            hour_reset_at = NOW() + INTERVAL '1 hour'
        WHERE user_id = p_user_id;
    END IF;

    -- Retornar limites actualizados
    SELECT * INTO v_limits FROM llm.llm_usage_limits WHERE user_id = p_user_id;
    RETURN v_limits;
END;
$$ LANGUAGE plpgsql;

-- Funcion para verificar si puede enviar mensaje
CREATE OR REPLACE FUNCTION llm.can_send_message(p_user_id UUID)
RETURNS TABLE (
    allowed BOOLEAN,
    reason TEXT,
    messages_remaining INTEGER,
    tokens_remaining INTEGER
) AS $$
DECLARE
    v_limits llm.llm_usage_limits;
BEGIN
    v_limits := llm.check_and_reset_limits(p_user_id);

    IF v_limits IS NULL THEN
        RETURN QUERY SELECT FALSE, 'User limits not configured'::TEXT, 0, 0;
        RETURN;
    END IF;

    IF v_limits.is_suspended THEN
        RETURN QUERY SELECT FALSE, v_limits.suspension_reason, 0, 0;
        RETURN;
    END IF;

    IF NOT v_limits.is_active THEN
        RETURN QUERY SELECT FALSE, 'Account inactive'::TEXT, 0, 0;
        RETURN;
    END IF;

    IF v_limits.messages_used >= v_limits.messages_limit AND NOT v_limits.allow_overage THEN
        RETURN QUERY SELECT FALSE, 'Message limit reached'::TEXT,
            0, v_limits.tokens_limit - v_limits.tokens_used;
        RETURN;
    END IF;

    IF v_limits.current_minute_requests >= v_limits.requests_per_minute THEN
        RETURN QUERY SELECT FALSE, 'Rate limit: too many requests per minute'::TEXT,
            v_limits.messages_limit - v_limits.messages_used,
            v_limits.tokens_limit - v_limits.tokens_used;
        RETURN;
    END IF;

    RETURN QUERY SELECT TRUE, NULL::TEXT,
        v_limits.messages_limit - v_limits.messages_used,
        v_limits.tokens_limit - v_limits.tokens_used;
END;
$$ LANGUAGE plpgsql;

-- Funcion para incrementar uso
CREATE OR REPLACE FUNCTION llm.increment_usage(
    p_user_id UUID,
    p_messages INTEGER DEFAULT 1,
    p_tokens INTEGER DEFAULT 0,
    p_tools INTEGER DEFAULT 0,
    p_cost_usd DECIMAL DEFAULT 0
)
RETURNS VOID AS $$
BEGIN
    UPDATE llm.llm_usage_limits
    SET messages_used = messages_used + p_messages,
        tokens_used = tokens_used + p_tokens,
        tools_used = tools_used + p_tools,
        monthly_spent_usd = monthly_spent_usd + p_cost_usd,
        current_minute_requests = current_minute_requests + 1,
        current_hour_requests = current_hour_requests + 1
    WHERE user_id = p_user_id;
END;
$$ LANGUAGE plpgsql;

-- Vista de uso actual
CREATE OR REPLACE VIEW llm.v_current_usage AS
SELECT
    user_id,
    vip_level,
    messages_used,
    messages_limit,
    ROUND((messages_used::DECIMAL / NULLIF(messages_limit, 0) * 100), 1) AS messages_percent,
    tokens_used,
    tokens_limit,
    ROUND((tokens_used::DECIMAL / NULLIF(tokens_limit, 0) * 100), 1) AS tokens_percent,
    tools_used,
    tools_limit,
    monthly_spent_usd,
    monthly_budget_usd,
    messages_reset_at,
    tokens_reset_at,
    is_active,
    is_suspended
FROM llm.llm_usage_limits;

-- Vista de usuarios cerca del limite
CREATE OR REPLACE VIEW llm.v_users_near_limit AS
SELECT
    user_id,
    vip_level,
    messages_used,
    messages_limit,
    ROUND((messages_used::DECIMAL / NULLIF(messages_limit, 0) * 100), 1) AS messages_percent,
    tokens_used,
    tokens_limit,
    ROUND((tokens_used::DECIMAL / NULLIF(tokens_limit, 0) * 100), 1) AS tokens_percent
FROM llm.llm_usage_limits
WHERE (messages_used::DECIMAL / NULLIF(messages_limit, 0) >= 0.8)
   OR (tokens_used::DECIMAL / NULLIF(tokens_limit, 0) >= 0.8)
ORDER BY
    GREATEST(
        messages_used::DECIMAL / NULLIF(messages_limit, 0),
        tokens_used::DECIMAL / NULLIF(tokens_limit, 0)
    ) DESC;

-- RLS Policies
ALTER TABLE llm.llm_usage_limits ENABLE ROW LEVEL SECURITY;

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

CREATE POLICY usage_limits_user ON llm.llm_usage_limits
    FOR SELECT
    USING (user_id = current_setting('app.current_user_id', true)::UUID);

-- Grants
GRANT SELECT, INSERT, UPDATE ON llm.llm_usage_limits TO trading_app;
GRANT SELECT ON llm.llm_usage_limits TO trading_readonly;
GRANT SELECT ON llm.v_current_usage TO trading_app;
GRANT SELECT ON llm.v_users_near_limit TO trading_app;
GRANT EXECUTE ON FUNCTION llm.check_and_reset_limits TO trading_app;
GRANT EXECUTE ON FUNCTION llm.can_send_message TO trading_app;
GRANT EXECUTE ON FUNCTION llm.increment_usage TO trading_app;
