-- Migration 003: Add composite and covering indexes for performance
-- Date: 2025-12-14
-- Description: Create optimized indexes for frequent queries

-- =====================================================
-- 1. User-Pack relationship indexes
-- =====================================================

-- Composite index for "get user packs" query
CREATE INDEX IF NOT EXISTS idx_user_packs_composite 
ON user_packs(user_id, pack_id) 
INCLUDE (granted_at, grant_type);

-- Reverse index for "which users have this pack"
CREATE INDEX IF NOT EXISTS idx_user_packs_pack_composite 
ON user_packs(pack_id, user_id) 
WHERE grant_type != 'admin';

-- =====================================================
-- 2. Card-Pack relationship indexes
-- =====================================================

-- Composite index for "get pack cards ordered"
CREATE INDEX IF NOT EXISTS idx_card_pack_cards_ordered 
ON card_pack_cards(pack_id, "order") 
INCLUDE (card_id);

-- Index for cards
CREATE INDEX IF NOT EXISTS idx_game_cards_pack 
ON game_cards(pack_id) 
INCLUDE (original, translation) 
WHERE is_deleted = false;

-- =====================================================
-- 3. User subscriptions indexes
-- =====================================================

-- Active subscriptions (most common query)
CREATE INDEX IF NOT EXISTS idx_user_subscriptions_active 
ON user_subscriptions(user_id, finish) 
WHERE finish > NOW();

-- Index for subscription expiration checks
CREATE INDEX IF NOT EXISTS idx_user_subscriptions_expiring 
ON user_subscriptions(finish) 
WHERE finish BETWEEN NOW() AND NOW() + INTERVAL '7 days';

-- =====================================================
-- 4. Payments indexes
-- =====================================================

-- Covering index for user payments list
CREATE INDEX IF NOT EXISTS idx_payments_user_covering 
ON payments(user_id, date DESC) 
INCLUDE (amount, currency, status, payment_system);

-- Index for pending/processing payments (for cron jobs)
CREATE INDEX IF NOT EXISTS idx_payments_pending 
ON payments(status, date DESC) 
WHERE status IN ('pending', 'processing', 'created');

-- Index for successful payments analytics
CREATE INDEX IF NOT EXISTS idx_payments_succeeded 
ON payments(date DESC) 
WHERE status = 'succeeded';

-- =====================================================
-- 5. Study sessions indexes
-- =====================================================

-- User sessions ordered by time
CREATE INDEX IF NOT EXISTS idx_study_sessions_user_time 
ON study_sessions(user_id, start_time DESC) 
INCLUDE (words_learned, tests_completed, accuracy);

-- Active sessions (no end_time yet)
CREATE INDEX IF NOT EXISTS idx_study_sessions_active 
ON study_sessions(user_id, session_id) 
WHERE end_time IS NULL;

-- =====================================================
-- 6. Authentication indexes
-- =====================================================

-- Covering index for token lookup
CREATE INDEX IF NOT EXISTS idx_tokens_token_covering 
ON tokens(token) 
INCLUDE (user_id, expires, external_user_id);

-- Valid tokens only
CREATE INDEX IF NOT EXISTS idx_tokens_valid 
ON tokens(user_id, expires DESC) 
WHERE expires > NOW();

-- Refresh tokens covering index
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_jti_covering 
ON refresh_tokens(jti) 
INCLUDE (user_id, expires_at, is_blacklisted);

-- Active refresh tokens
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_active 
ON refresh_tokens(user_id, expires_at DESC) 
WHERE is_blacklisted = false AND expires_at > NOW();

-- =====================================================
-- 7. Users indexes
-- =====================================================

-- Email lookup with common fields
CREATE INDEX IF NOT EXISTS idx_users_email_covering 
ON users(email) 
INCLUDE (id, name, admin, external_user_id) 
WHERE email IS NOT NULL AND is_deleted = false;

-- External user ID lookup
CREATE INDEX IF NOT EXISTS idx_users_external_covering 
ON users(external_user_id) 
INCLUDE (id, name, email, admin);

-- =====================================================
-- 8. Promo codes indexes
-- =====================================================

-- Code lookup
CREATE INDEX IF NOT EXISTS idx_promo_codes_code_covering 
ON promo_codes(code) 
INCLUDE (campaign_id, user_id, is_used) 
WHERE is_used = false;

-- Campaign promo codes
CREATE INDEX IF NOT EXISTS idx_promo_codes_campaign 
ON promo_codes(campaign_id, is_used);

-- =====================================================
-- 9. Tests indexes
-- =====================================================

-- Test pack relation
CREATE INDEX IF NOT EXISTS idx_test_pack_relations_pack 
ON test_pack_relations(pack_id) 
INCLUDE (test_id);

-- Test questions ordered
CREATE INDEX IF NOT EXISTS idx_test_questions_test 
ON test_questions(test_id, "order");

-- =====================================================
-- Analyze tables after creating indexes
-- =====================================================
ANALYZE users;
ANALYZE user_packs;
ANALYZE card_pack_cards;
ANALYZE game_cards;
ANALYZE payments;
ANALYZE user_subscriptions;
ANALYZE study_sessions;
ANALYZE tokens;
ANALYZE refresh_tokens;
ANALYZE promo_codes;
ANALYZE tests;

-- =====================================================
-- Проверка созданных индексов
-- =====================================================
SELECT 
  schemaname,
  tablename,
  indexname,
  indexdef
FROM pg_indexes
WHERE schemaname = 'public'
  AND indexname LIKE 'idx_%composite%' 
  OR indexname LIKE 'idx_%covering%'
ORDER BY tablename, indexname;

-- =====================================================
-- Оценка размера индексов
-- =====================================================
SELECT 
  schemaname,
  tablename,
  indexname,
  pg_size_pretty(pg_relation_size(indexrelid)) as index_size
FROM pg_stat_user_indexes
WHERE schemaname = 'public'
ORDER BY pg_relation_size(indexrelid) DESC
LIMIT 20;

PRINT 'Migration 003 completed successfully!';









