-- ================================================================================================
-- Migration 025: Query Performance Optimization
-- Additional optimizations for paper trading validation queries
-- ================================================================================================
-- Target: <5ms P99 query latency for aggregation queries, optimize TimescaleDB chunk exclusion
-- ================================================================================================

-- ================================================================================================
-- PART 1: ENHANCED COMPOSITE INDEXES FOR COMMON QUERY PATTERNS
-- ================================================================================================

-- Note: TimescaleDB hypertables do not support CONCURRENTLY, using regular CREATE INDEX
-- Optimize symbol-filtered aggregation queries (from paper trading validation)
-- Pattern: WHERE symbol = ? AND timestamp > ?
CREATE INDEX IF NOT EXISTS idx_ensemble_predictions_symbol_time
ON ensemble_predictions (symbol, timestamp DESC)
WHERE timestamp > NOW() - INTERVAL '30 days';

-- Optimize real-time dashboard queries (last 24 hours)
-- Pattern: WHERE timestamp > NOW() - INTERVAL '1 day'
-- Note: This is a partial index covering only recent data for faster scans
CREATE INDEX IF NOT EXISTS idx_ensemble_predictions_recent_24h
ON ensemble_predictions (timestamp DESC)
INCLUDE (ensemble_confidence, disagreement_rate, ensemble_action, symbol)
WHERE timestamp > NOW() - INTERVAL '24 hours';

-- Optimize model-specific queries (individual model performance)
-- Pattern: WHERE dqn_signal IS NOT NULL
CREATE INDEX IF NOT EXISTS idx_ensemble_predictions_dqn_active
ON ensemble_predictions (timestamp DESC)
WHERE dqn_signal IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_ensemble_predictions_ppo_active
ON ensemble_predictions (timestamp DESC)
WHERE ppo_signal IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_ensemble_predictions_mamba2_active
ON ensemble_predictions (timestamp DESC)
WHERE mamba2_signal IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_ensemble_predictions_tft_active
ON ensemble_predictions (timestamp DESC)
WHERE tft_signal IS NOT NULL;

-- Optimize order execution tracking (predictions that converted to orders)
CREATE INDEX IF NOT EXISTS idx_ensemble_predictions_executed
ON ensemble_predictions (timestamp DESC)
INCLUDE (order_id, executed_price, position_size, pnl)
WHERE order_id IS NOT NULL;

-- ================================================================================================
-- PART 2: MATERIALIZED VIEWS FOR SLOW AGGREGATION QUERIES
-- ================================================================================================

-- Real-time model activity summary (for debugging NULL model votes)
-- Refreshes every minute to catch inactive models quickly
DROP MATERIALIZED VIEW IF EXISTS model_activity_realtime CASCADE;

CREATE MATERIALIZED VIEW model_activity_realtime AS
SELECT
    time_bucket('1 minute', timestamp) AS minute,
    symbol,
    COUNT(*) AS total_predictions,
    COUNT(dqn_signal) AS dqn_active_count,
    COUNT(ppo_signal) AS ppo_active_count,
    COUNT(mamba2_signal) AS mamba2_active_count,
    COUNT(tft_signal) AS tft_active_count,
    ROUND(100.0 * COUNT(dqn_signal) / NULLIF(COUNT(*), 0), 2) AS dqn_active_pct,
    ROUND(100.0 * COUNT(ppo_signal) / NULLIF(COUNT(*), 0), 2) AS ppo_active_pct,
    ROUND(100.0 * COUNT(mamba2_signal) / NULLIF(COUNT(*), 0), 2) AS mamba2_active_pct,
    ROUND(100.0 * COUNT(tft_signal) / NULLIF(COUNT(*), 0), 2) AS tft_active_pct,
    AVG(ensemble_confidence) AS avg_confidence,
    AVG(disagreement_rate) AS avg_disagreement
FROM ensemble_predictions
WHERE timestamp > NOW() - INTERVAL '1 hour'
GROUP BY minute, symbol
ORDER BY minute DESC;

CREATE INDEX ON model_activity_realtime (minute DESC);
CREATE INDEX ON model_activity_realtime (symbol);

COMMENT ON MATERIALIZED VIEW model_activity_realtime IS 'Real-time model activity tracking (last 1 hour, 1-minute buckets)';

-- Paper trading execution summary (for monitoring order conversion rate)
DROP MATERIALIZED VIEW IF EXISTS paper_trading_execution_summary CASCADE;

CREATE MATERIALIZED VIEW paper_trading_execution_summary AS
SELECT
    time_bucket('5 minutes', timestamp) AS bucket,
    symbol,
    COUNT(*) AS total_predictions,
    COUNT(order_id) AS executed_orders,
    ROUND(100.0 * COUNT(order_id) / NULLIF(COUNT(*), 0), 2) AS execution_rate_pct,
    COUNT(CASE WHEN pnl > 0 THEN 1 END) AS winning_trades,
    COUNT(CASE WHEN pnl < 0 THEN 1 END) AS losing_trades,
    COUNT(CASE WHEN pnl IS NOT NULL THEN 1 END) AS total_trades,
    ROUND(100.0 * COUNT(CASE WHEN pnl > 0 THEN 1 END) / NULLIF(COUNT(CASE WHEN pnl IS NOT NULL THEN 1 END), 0), 2) AS win_rate_pct,
    SUM(pnl) AS total_pnl,
    AVG(pnl) AS avg_pnl,
    STDDEV(pnl) AS stddev_pnl,
    MIN(pnl) AS worst_trade,
    MAX(pnl) AS best_trade
FROM ensemble_predictions
WHERE timestamp > NOW() - INTERVAL '24 hours'
GROUP BY bucket, symbol
ORDER BY bucket DESC;

CREATE INDEX ON paper_trading_execution_summary (bucket DESC);
CREATE INDEX ON paper_trading_execution_summary (symbol);

COMMENT ON MATERIALIZED VIEW paper_trading_execution_summary IS 'Paper trading execution rate and P&L summary (last 24 hours)';

-- ================================================================================================
-- PART 3: OPTIMIZED QUERY FUNCTIONS (PRE-COMPUTED AGGREGATIONS)
-- ================================================================================================

-- Fast aggregation function for real-time dashboard queries
-- Uses continuous aggregates instead of scanning raw table
CREATE OR REPLACE FUNCTION get_ensemble_performance_summary(
    p_interval INTERVAL DEFAULT INTERVAL '1 day',
    p_symbol VARCHAR(20) DEFAULT NULL
)
RETURNS TABLE (
    avg_confidence DOUBLE PRECISION,
    avg_disagreement DOUBLE PRECISION,
    total_predictions BIGINT,
    total_trades BIGINT,
    win_rate DOUBLE PRECISION,
    total_pnl NUMERIC,
    avg_latency_us NUMERIC,
    p99_latency_us DOUBLE PRECISION
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        AVG(ep5m.avg_confidence)::DOUBLE PRECISION,
        AVG(ep5m.avg_disagreement)::DOUBLE PRECISION,
        SUM(ep5m.prediction_count)::BIGINT,
        SUM(ep5m.total_trades)::BIGINT,
        (100.0 * SUM(ep5m.winning_trades) / NULLIF(SUM(ep5m.total_trades), 0))::DOUBLE PRECISION,
        SUM(ep5m.total_pnl),
        AVG(ep5m.avg_latency_us),
        MAX(ep5m.p99_latency_us)::DOUBLE PRECISION
    FROM ensemble_performance_5min ep5m
    WHERE ep5m.bucket > NOW() - p_interval
        AND (p_symbol IS NULL OR ep5m.symbol = p_symbol);
END;
$$ LANGUAGE plpgsql STABLE;

COMMENT ON FUNCTION get_ensemble_performance_summary IS 'Fast aggregation using continuous aggregates (avoids raw table scan)';

-- Model activity health check function
-- Quickly identifies inactive models
CREATE OR REPLACE FUNCTION check_model_activity_health(
    p_lookback_minutes INTEGER DEFAULT 60
)
RETURNS TABLE (
    model_name VARCHAR(20),
    is_active BOOLEAN,
    last_prediction_time TIMESTAMPTZ,
    minutes_since_last_prediction INTEGER,
    predictions_in_window BIGINT,
    activity_rate_pct DOUBLE PRECISION
) AS $$
BEGIN
    RETURN QUERY
    WITH recent_predictions AS (
        SELECT
            timestamp,
            dqn_signal IS NOT NULL AS dqn_active,
            ppo_signal IS NOT NULL AS ppo_active,
            mamba2_signal IS NOT NULL AS mamba2_active,
            tft_signal IS NOT NULL AS tft_active
        FROM ensemble_predictions
        WHERE timestamp > NOW() - INTERVAL '1 minute' * p_lookback_minutes
    ),
    model_stats AS (
        SELECT
            'DQN' AS model,
            MAX(CASE WHEN dqn_active THEN timestamp END) AS last_pred,
            COUNT(CASE WHEN dqn_active THEN 1 END) AS pred_count,
            COUNT(*) AS total_count
        FROM recent_predictions
        UNION ALL
        SELECT
            'PPO',
            MAX(CASE WHEN ppo_active THEN timestamp END),
            COUNT(CASE WHEN ppo_active THEN 1 END),
            COUNT(*)
        FROM recent_predictions
        UNION ALL
        SELECT
            'MAMBA-2',
            MAX(CASE WHEN mamba2_active THEN timestamp END),
            COUNT(CASE WHEN mamba2_active THEN 1 END),
            COUNT(*)
        FROM recent_predictions
        UNION ALL
        SELECT
            'TFT',
            MAX(CASE WHEN tft_active THEN timestamp END),
            COUNT(CASE WHEN tft_active THEN 1 END),
            COUNT(*)
        FROM recent_predictions
    )
    SELECT
        ms.model::VARCHAR(20),
        (ms.pred_count > 0)::BOOLEAN,
        ms.last_pred,
        EXTRACT(EPOCH FROM (NOW() - COALESCE(ms.last_pred, NOW() - INTERVAL '1 year')))::INTEGER / 60,
        ms.pred_count::BIGINT,
        (100.0 * ms.pred_count / NULLIF(ms.total_count, 0))::DOUBLE PRECISION
    FROM model_stats ms;
END;
$$ LANGUAGE plpgsql STABLE;

COMMENT ON FUNCTION check_model_activity_health IS 'Quickly identifies inactive models (NULL signal issue)';

-- ================================================================================================
-- PART 4: QUERY PERFORMANCE MONITORING
-- ================================================================================================

-- Create extension for query statistics if not exists
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- View for monitoring slow queries (updated from migration 023)
CREATE OR REPLACE VIEW ensemble_slow_queries AS
SELECT
    LEFT(query, 150) AS query_preview,
    calls,
    ROUND(total_exec_time::NUMERIC / 1000.0, 2) AS total_time_sec,
    ROUND(mean_exec_time::NUMERIC, 2) AS avg_time_ms,
    ROUND(max_exec_time::NUMERIC, 2) AS max_time_ms,
    ROUND(stddev_exec_time::NUMERIC, 2) AS stddev_time_ms,
    rows / NULLIF(calls, 0) AS avg_rows_per_call,
    ROUND(100.0 * shared_blks_hit / NULLIF(shared_blks_hit + shared_blks_read, 0), 2) AS cache_hit_ratio
FROM pg_stat_statements
WHERE query LIKE '%ensemble_predictions%'
   OR query LIKE '%model_performance_attribution%'
   OR query LIKE '%paper_trading_predictions%'
ORDER BY mean_exec_time DESC
LIMIT 30;

COMMENT ON VIEW ensemble_slow_queries IS 'Top 30 slowest ensemble/paper trading queries with cache hit ratio';

-- ================================================================================================
-- PART 5: VACUUM AND ANALYZE OPTIMIZATION
-- ================================================================================================

-- Optimize autovacuum settings for high-write tables
ALTER TABLE ensemble_predictions SET (
    autovacuum_vacuum_scale_factor = 0.05,  -- Vacuum when 5% of rows change (default 20%)
    autovacuum_analyze_scale_factor = 0.025, -- Analyze when 2.5% change (default 10%)
    autovacuum_vacuum_cost_delay = 10        -- Speed up vacuum (default 20ms)
);

ALTER TABLE model_performance_attribution SET (
    autovacuum_vacuum_scale_factor = 0.05,
    autovacuum_analyze_scale_factor = 0.025,
    autovacuum_vacuum_cost_delay = 10
);

ALTER TABLE paper_trading_predictions SET (
    autovacuum_vacuum_scale_factor = 0.05,
    autovacuum_analyze_scale_factor = 0.025,
    autovacuum_vacuum_cost_delay = 10
);

-- Force immediate vacuum and analyze
VACUUM ANALYZE ensemble_predictions;
VACUUM ANALYZE model_performance_attribution;
VACUUM ANALYZE paper_trading_predictions;

-- ================================================================================================
-- PART 6: GRANT PERMISSIONS
-- ================================================================================================

GRANT SELECT ON model_activity_realtime TO foxhunt;
GRANT SELECT ON paper_trading_execution_summary TO foxhunt;
GRANT SELECT ON ensemble_slow_queries TO foxhunt;
GRANT EXECUTE ON FUNCTION get_ensemble_performance_summary TO foxhunt;
GRANT EXECUTE ON FUNCTION check_model_activity_health TO foxhunt;

-- ================================================================================================
-- PART 7: REFRESH MATERIALIZED VIEWS
-- ================================================================================================

REFRESH MATERIALIZED VIEW model_activity_realtime;
REFRESH MATERIALIZED VIEW paper_trading_execution_summary;

-- ================================================================================================
-- END MIGRATION 025
-- ================================================================================================
