-- ============================================================================
-- SCHEMA: ml
-- TABLES: prediction_purchases, prediction_outcomes, prediction_packages
-- DESCRIPTION: Extensiones para marketplace de predicciones ML
-- VERSION: 1.0.0
-- CREATED: 2026-01-10
-- DEPENDS: ml schema (existing), financial.wallets, products.products
-- ============================================================================

-- Asume que ml schema ya existe con predictions table

-- Enum para estado de outcome
DO $$ BEGIN
    CREATE TYPE ml.outcome_status AS ENUM (
        'pending',      -- Esperando resultado
        'win',          -- Prediccion acertada
        'loss',         -- Prediccion fallida
        'partial',      -- Parcialmente correcta
        'cancelled',    -- Cancelada (mercado cerrado, etc)
        'expired'       -- Expirada sin resultado
    );
EXCEPTION WHEN duplicate_object THEN null; END $$;

-- Enum para tipo de prediccion comprada
DO $$ BEGIN
    CREATE TYPE ml.purchase_source AS ENUM (
        'INDIVIDUAL',       -- Compra individual
        'SUBSCRIPTION',     -- Incluida en suscripcion
        'VIP_ACCESS',       -- Acceso VIP
        'PACKAGE',          -- Parte de un paquete
        'PROMO'             -- Promocional/gratuita
    );
EXCEPTION WHEN duplicate_object THEN null; END $$;

-- Tabla de paquetes de predicciones (productos vendibles)
CREATE TABLE IF NOT EXISTS ml.prediction_packages (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    -- Identificacion
    name VARCHAR(200) NOT NULL,
    slug VARCHAR(100) UNIQUE NOT NULL,
    description TEXT,

    -- Configuracion del paquete
    model_id VARCHAR(100) NOT NULL,
    model_name VARCHAR(200),
    prediction_count INT NOT NULL CHECK (prediction_count > 0),

    -- Simbolos incluidos (NULL = todos disponibles)
    included_symbols VARCHAR[] DEFAULT NULL,

    -- Timeframes incluidos
    included_timeframes VARCHAR[] DEFAULT '{H1,H4,D1}',

    -- Precios
    price DECIMAL(10, 2) NOT NULL CHECK (price >= 0),
    compare_price DECIMAL(10, 2),

    -- Validez
    validity_days INT DEFAULT 30,

    -- Stripe
    stripe_product_id VARCHAR(255),
    stripe_price_id VARCHAR(255),

    -- Display
    badge VARCHAR(50),
    is_popular BOOLEAN DEFAULT FALSE,
    sort_order INT DEFAULT 0,

    -- Estado
    is_active BOOLEAN DEFAULT TRUE,

    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

COMMENT ON TABLE ml.prediction_packages IS
'Paquetes de predicciones vendibles (ej: 10 predicciones AMD por $29)';

-- Tabla de compras de predicciones
CREATE TABLE IF NOT EXISTS ml.prediction_purchases (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    user_id UUID NOT NULL,

    -- Origen de la compra
    source ml.purchase_source NOT NULL,

    -- Referencias segun origen
    package_id UUID REFERENCES ml.prediction_packages(id),
    product_purchase_id UUID,  -- Ref a products.purchases
    vip_subscription_id UUID,  -- Ref a vip.subscriptions

    -- Modelo y configuracion
    model_id VARCHAR(100) NOT NULL,
    model_name VARCHAR(200),

    -- Creditos de predicciones
    predictions_total INT NOT NULL CHECK (predictions_total > 0),
    predictions_used INT NOT NULL DEFAULT 0 CHECK (predictions_used >= 0),
    predictions_remaining INT GENERATED ALWAYS AS (predictions_total - predictions_used) STORED,

    -- Pago
    amount_paid DECIMAL(10, 2) NOT NULL DEFAULT 0,
    wallet_transaction_id UUID,

    -- Validez
    valid_from TIMESTAMPTZ DEFAULT NOW(),
    valid_until TIMESTAMPTZ,

    -- Estado
    is_active BOOLEAN DEFAULT TRUE,

    -- Metadata
    metadata JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT NOW(),

    CONSTRAINT valid_predictions CHECK (predictions_used <= predictions_total)
);

COMMENT ON TABLE ml.prediction_purchases IS
'Registro de compras/accesos a predicciones por usuario';

-- Tabla de predicciones generadas y sus resultados
CREATE TABLE IF NOT EXISTS ml.prediction_outcomes (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    user_id UUID NOT NULL,

    -- Referencia a la compra
    purchase_id UUID NOT NULL REFERENCES ml.prediction_purchases(id),

    -- Referencia a la prediccion original (si existe en ml.predictions)
    prediction_id UUID,

    -- Datos de la prediccion
    model_id VARCHAR(100) NOT NULL,
    symbol VARCHAR(20) NOT NULL,
    timeframe VARCHAR(10) NOT NULL,

    -- Prediccion
    predicted_direction VARCHAR(10) NOT NULL, -- BUY, SELL, NEUTRAL
    predicted_price DECIMAL(20, 8),
    confidence DECIMAL(5, 4) CHECK (confidence >= 0 AND confidence <= 1),

    -- Niveles predichos
    entry_price DECIMAL(20, 8),
    take_profit DECIMAL(20, 8),
    stop_loss DECIMAL(20, 8),

    -- Resultado
    outcome_status ml.outcome_status NOT NULL DEFAULT 'pending',
    actual_direction VARCHAR(10),
    actual_price DECIMAL(20, 8),

    -- Metricas de resultado
    pips_result DECIMAL(10, 2),
    percentage_result DECIMAL(10, 4),
    hit_tp BOOLEAN,
    hit_sl BOOLEAN,

    -- Tiempos
    predicted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    target_time TIMESTAMPTZ,
    resolved_at TIMESTAMPTZ,

    -- Metadata adicional del modelo
    model_metadata JSONB DEFAULT '{}',

    created_at TIMESTAMPTZ DEFAULT NOW()
);

COMMENT ON TABLE ml.prediction_outcomes IS
'Historial de predicciones generadas con sus resultados para validacion';

-- Indices para prediction_packages
CREATE INDEX IF NOT EXISTS idx_pred_pkg_model ON ml.prediction_packages(model_id);
CREATE INDEX IF NOT EXISTS idx_pred_pkg_active ON ml.prediction_packages(is_active) WHERE is_active = TRUE;
CREATE INDEX IF NOT EXISTS idx_pred_pkg_slug ON ml.prediction_packages(slug);

-- Indices para prediction_purchases
CREATE INDEX IF NOT EXISTS idx_pred_purch_tenant ON ml.prediction_purchases(tenant_id);
CREATE INDEX IF NOT EXISTS idx_pred_purch_user ON ml.prediction_purchases(user_id);
CREATE INDEX IF NOT EXISTS idx_pred_purch_model ON ml.prediction_purchases(model_id);
CREATE INDEX IF NOT EXISTS idx_pred_purch_active ON ml.prediction_purchases(is_active, valid_until)
    WHERE is_active = TRUE;
CREATE INDEX IF NOT EXISTS idx_pred_purch_source ON ml.prediction_purchases(source);

-- Indices para prediction_outcomes
CREATE INDEX IF NOT EXISTS idx_pred_out_tenant ON ml.prediction_outcomes(tenant_id);
CREATE INDEX IF NOT EXISTS idx_pred_out_user ON ml.prediction_outcomes(user_id);
CREATE INDEX IF NOT EXISTS idx_pred_out_purchase ON ml.prediction_outcomes(purchase_id);
CREATE INDEX IF NOT EXISTS idx_pred_out_status ON ml.prediction_outcomes(outcome_status);
CREATE INDEX IF NOT EXISTS idx_pred_out_symbol ON ml.prediction_outcomes(symbol);
CREATE INDEX IF NOT EXISTS idx_pred_out_predicted ON ml.prediction_outcomes(predicted_at DESC);
CREATE INDEX IF NOT EXISTS idx_pred_out_model ON ml.prediction_outcomes(model_id);

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

DROP TRIGGER IF EXISTS pred_pkg_updated ON ml.prediction_packages;
CREATE TRIGGER pred_pkg_updated
    BEFORE UPDATE ON ml.prediction_packages
    FOR EACH ROW
    EXECUTE FUNCTION ml.update_pkg_timestamp();

-- Funcion para consumir una prediccion
CREATE OR REPLACE FUNCTION ml.consume_prediction(
    p_purchase_id UUID,
    p_symbol VARCHAR(20),
    p_timeframe VARCHAR(10),
    p_direction VARCHAR(10),
    p_confidence DECIMAL(5, 4),
    p_entry DECIMAL(20, 8) DEFAULT NULL,
    p_tp DECIMAL(20, 8) DEFAULT NULL,
    p_sl DECIMAL(20, 8) DEFAULT NULL,
    p_target_time TIMESTAMPTZ DEFAULT NULL,
    p_metadata JSONB DEFAULT '{}'
)
RETURNS UUID AS $$
DECLARE
    v_purchase ml.prediction_purchases%ROWTYPE;
    v_outcome_id UUID;
BEGIN
    -- Lock and get purchase
    SELECT * INTO v_purchase
    FROM ml.prediction_purchases
    WHERE id = p_purchase_id
    FOR UPDATE;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Purchase not found: %', p_purchase_id;
    END IF;

    IF NOT v_purchase.is_active THEN
        RAISE EXCEPTION 'Purchase is not active';
    END IF;

    IF v_purchase.valid_until IS NOT NULL AND v_purchase.valid_until < NOW() THEN
        RAISE EXCEPTION 'Purchase has expired';
    END IF;

    IF v_purchase.predictions_used >= v_purchase.predictions_total THEN
        RAISE EXCEPTION 'No predictions remaining in this purchase';
    END IF;

    -- Create outcome record
    INSERT INTO ml.prediction_outcomes (
        tenant_id, user_id, purchase_id, model_id,
        symbol, timeframe, predicted_direction, confidence,
        entry_price, take_profit, stop_loss, target_time,
        model_metadata
    ) VALUES (
        v_purchase.tenant_id, v_purchase.user_id, p_purchase_id, v_purchase.model_id,
        p_symbol, p_timeframe, p_direction, p_confidence,
        p_entry, p_tp, p_sl, p_target_time,
        p_metadata
    ) RETURNING id INTO v_outcome_id;

    -- Update purchase counter
    UPDATE ml.prediction_purchases
    SET predictions_used = predictions_used + 1
    WHERE id = p_purchase_id;

    RETURN v_outcome_id;
END;
$$ LANGUAGE plpgsql;

-- Funcion para resolver outcome
CREATE OR REPLACE FUNCTION ml.resolve_prediction_outcome(
    p_outcome_id UUID,
    p_status ml.outcome_status,
    p_actual_direction VARCHAR(10) DEFAULT NULL,
    p_actual_price DECIMAL(20, 8) DEFAULT NULL,
    p_pips DECIMAL(10, 2) DEFAULT NULL,
    p_percentage DECIMAL(10, 4) DEFAULT NULL,
    p_hit_tp BOOLEAN DEFAULT NULL,
    p_hit_sl BOOLEAN DEFAULT NULL
)
RETURNS VOID AS $$
BEGIN
    UPDATE ml.prediction_outcomes SET
        outcome_status = p_status,
        actual_direction = p_actual_direction,
        actual_price = p_actual_price,
        pips_result = p_pips,
        percentage_result = p_percentage,
        hit_tp = p_hit_tp,
        hit_sl = p_hit_sl,
        resolved_at = NOW()
    WHERE id = p_outcome_id;
END;
$$ LANGUAGE plpgsql;

-- Funcion para obtener estadisticas de predicciones por usuario
CREATE OR REPLACE FUNCTION ml.get_user_prediction_stats(
    p_user_id UUID,
    p_model_id VARCHAR(100) DEFAULT NULL
)
RETURNS TABLE (
    total_predictions BIGINT,
    wins BIGINT,
    losses BIGINT,
    pending BIGINT,
    win_rate DECIMAL(5, 2),
    avg_pips DECIMAL(10, 2),
    avg_confidence DECIMAL(5, 4)
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        COUNT(*)::BIGINT as total_predictions,
        COUNT(*) FILTER (WHERE outcome_status = 'win')::BIGINT as wins,
        COUNT(*) FILTER (WHERE outcome_status = 'loss')::BIGINT as losses,
        COUNT(*) FILTER (WHERE outcome_status = 'pending')::BIGINT as pending,
        CASE
            WHEN COUNT(*) FILTER (WHERE outcome_status IN ('win', 'loss')) > 0
            THEN ROUND(
                COUNT(*) FILTER (WHERE outcome_status = 'win')::DECIMAL * 100 /
                COUNT(*) FILTER (WHERE outcome_status IN ('win', 'loss')),
                2
            )
            ELSE 0
        END as win_rate,
        ROUND(AVG(pips_result) FILTER (WHERE pips_result IS NOT NULL), 2) as avg_pips,
        ROUND(AVG(confidence), 4) as avg_confidence
    FROM ml.prediction_outcomes
    WHERE user_id = p_user_id
      AND (p_model_id IS NULL OR model_id = p_model_id);
END;
$$ LANGUAGE plpgsql;

-- RLS
ALTER TABLE ml.prediction_packages ENABLE ROW LEVEL SECURITY;
ALTER TABLE ml.prediction_purchases ENABLE ROW LEVEL SECURITY;
ALTER TABLE ml.prediction_outcomes ENABLE ROW LEVEL SECURITY;

-- Packages visibles para todos
CREATE POLICY pred_pkg_read_all ON ml.prediction_packages
    FOR SELECT USING (TRUE);

-- Purchases por tenant
CREATE POLICY pred_purch_tenant ON ml.prediction_purchases
    FOR ALL
    USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);

-- Outcomes por tenant
CREATE POLICY pred_out_tenant ON ml.prediction_outcomes
    FOR ALL
    USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);

-- Grants
GRANT SELECT ON ml.prediction_packages TO trading_app;
GRANT SELECT, INSERT, UPDATE ON ml.prediction_purchases TO trading_app;
GRANT SELECT, INSERT, UPDATE ON ml.prediction_outcomes TO trading_app;
GRANT SELECT ON ml.prediction_packages TO trading_readonly;
GRANT SELECT ON ml.prediction_purchases TO trading_readonly;
GRANT SELECT ON ml.prediction_outcomes TO trading_readonly;
