import dotenv from 'dotenv';
dotenv.config({ path: __dirname + '/../../.env' });

import pool from '../db';
import { normalizeQuery } from './normalizer';
import { checkSafety } from './safety-filter';
import { computeTTL } from './ttl-strategy';
import { storeEntry, exactMatch, closeRedis } from './similarity';
import { flushDailyMetrics, pruneExpired, logEvent } from './metrics';

// Categories that should NOT be warmed during live games (TTL=0 anyway)
const LIVE_BLOCKED_CATEGORIES = ['props', 'odds', 'scores'];

/**
 * Cache warming worker — runs every 15 minutes (PM2 process).
 * Reads recent user/assistant message pairs from crm_messages,
 * normalizes them, and populates the cache proactively.
 */

const WARMING_INTERVAL = parseInt(process.env.CACHE_WARMING_INTERVAL_MS || '900000'); // 15 min
const LOOKBACK_MINUTES = 35; // Slightly more than interval to catch stragglers
const AGENTS = ['sportsclaw', 'cryptoclaw', 'main'];

async function warmCache(): Promise<{ warmed: number; skipped: number; errors: number }> {
  let warmed = 0, skipped = 0, errors = 0;

  for (const agent of AGENTS) {
    try {
      // Strategy: Top-N frequency patterns from last 7 days + recent messages
      // This targets the queries that repeat most (highest cache ROI)
      const result = await pool.query(`
        WITH top_patterns AS (
          -- Find the most frequently asked queries from last 7 days
          SELECT DISTINCT ON (LEFT(m.content, 200))
            m.id AS msg_id,
            m.content AS user_content,
            m.timestamp AS user_ts,
            s.agent,
            COUNT(*) OVER (PARTITION BY LEFT(m.content, 200)) AS freq
          FROM crm_messages m
          JOIN crm_sessions s ON m.session_id = s.id
          WHERE s.agent = $1
            AND m.role = 'user'
            AND m.content_type = 'text'
            AND m.content IS NOT NULL
            AND LENGTH(m.content) > 15
            AND m.timestamp > NOW() - interval '7 days'
          ORDER BY LEFT(m.content, 200), m.timestamp DESC
        ),
        ranked AS (
          SELECT * FROM top_patterns
          ORDER BY freq DESC
          LIMIT 100
        ),
        recent AS (
          -- Also include recent messages (last cycle window)
          SELECT
            m.id AS msg_id,
            m.content AS user_content,
            m.timestamp AS user_ts,
            s.agent,
            1 AS freq
          FROM crm_messages m
          JOIN crm_sessions s ON m.session_id = s.id
          WHERE s.agent = $1
            AND m.role = 'user'
            AND m.content_type = 'text'
            AND m.content IS NOT NULL
            AND LENGTH(m.content) > 0
            AND m.timestamp > NOW() - interval '${LOOKBACK_MINUTES} minutes'
        ),
        combined AS (
          SELECT * FROM ranked
          UNION ALL
          SELECT * FROM recent
        )
        SELECT DISTINCT ON (c.msg_id)
          c.msg_id,
          c.user_content,
          c.user_ts,
          c.agent,
          c.freq,
          resp.content AS assistant_content,
          resp.timestamp AS resp_ts
        FROM combined c
        LEFT JOIN crm_sessions s2 ON TRUE
        LEFT JOIN LATERAL (
          SELECT r.content, r.timestamp
          FROM crm_messages r
          JOIN crm_sessions rs ON r.session_id = rs.id
          WHERE r.session_id = (SELECT session_id FROM crm_messages WHERE id = c.msg_id)
            AND r.timestamp > c.user_ts
            AND r.timestamp < (c.user_ts + interval '5 minutes')
            AND r.role = 'assistant'
            AND r.content_type = 'text'
            AND r.content IS NOT NULL
            AND LENGTH(r.content) > 50
          ORDER BY r.timestamp ASC
          LIMIT 1
        ) resp ON true
        ORDER BY c.msg_id, c.freq DESC
      `, [agent]);

      for (const row of result.rows) {
        if (!row.assistant_content) {
          skipped++;
          continue;
        }

        try {
          // Normalize the user query
          const normalized = normalizeQuery(row.user_content, agent);
          if (!normalized) {
            skipped++;
            continue;
          }

          // Safety check
          const safety = checkSafety(normalized.normalized, row.assistant_content);
          if (!safety.isCacheable) {
            skipped++;
            continue;
          }

          // Check if already cached (exact match)
          const existing = await exactMatch(agent, normalized.cacheKey);
          if (existing) {
            skipped++;
            continue;
          }

          // Compute TTL (also gives us game state)
          const { ttlSeconds, gameState } = await computeTTL(
            normalized.category,
            normalized.league,
            normalized.teams,
            normalized.gameDate,
          );

          if (ttlSeconds === 0) {
            skipped++;
            continue;
          }

          // Skip live-game props/odds/scores — wasteful to warm volatile data
          if (gameState === 'live' && LIVE_BLOCKED_CATEGORIES.includes(normalized.category)) {
            skipped++;
            continue;
          }

          // Estimate response tokens
          const responseTokens = Math.ceil(row.assistant_content.length / 4);

          // Store in cache with full metadata
          const entryId = await storeEntry({
            cacheKey: normalized.cacheKey,
            normalizedQuery: normalized.normalized,
            originalQuery: row.user_content,
            agent,
            category: normalized.category,
            responsePayload: row.assistant_content,
            responseTokens,
            league: normalized.league,
            teams: normalized.teams,
            gameDate: normalized.gameDate,
            ttlSeconds,
            modelUsed: null,
            tokensIn: null,
            tokensOut: null,
            source: 'warm',
            dataVersion: normalized.dataVersion,
            gameState,
          });

          if (entryId) {
            warmed++;
            await logEvent({
              cacheEntryId: entryId,
              eventType: 'warm',
              agent,
              normalizedQuery: normalized.normalized,
            });
          }
        } catch (err) {
          errors++;
          console.error(`  Error warming message ${row.msg_id}:`, err);
        }
      }
    } catch (err) {
      errors++;
      console.error(`Error warming agent ${agent}:`, err);
    }
  }

  return { warmed, skipped, errors };
}

async function runCycle(): Promise<void> {
  const startTime = Date.now();
  console.log(`\n[${new Date().toISOString()}] Cache warming cycle starting...`);

  try {
    // 1. Warm cache from recent messages
    const stats = await warmCache();
    console.log(`  Warmed: ${stats.warmed}, Skipped: ${stats.skipped}, Errors: ${stats.errors}`);

    // 2. Prune expired entries
    const pruned = await pruneExpired();
    if (pruned > 0) console.log(`  Pruned ${pruned} expired entries`);

    // 3. Flush daily metrics
    await flushDailyMetrics();

    const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
    console.log(`  Cycle completed in ${elapsed}s`);
  } catch (err) {
    console.error('Warming cycle failed:', err);
  }
}

// Main: run once then every 15 minutes
async function main() {
  console.log('Cache Warming Worker starting...');
  console.log(`  Interval: ${WARMING_INTERVAL / 1000}s`);
  console.log(`  Lookback: ${LOOKBACK_MINUTES}m`);

  // Run immediately
  await runCycle();

  // Then on interval
  setInterval(async () => {
    try {
      await runCycle();
    } catch (err) {
      console.error('Warming cycle error:', err);
    }
  }, WARMING_INTERVAL);
}

main().catch((err) => {
  console.error('Warming worker fatal error:', err);
  process.exit(1);
});

// Graceful shutdown
process.on('SIGTERM', async () => {
  console.log('Warming worker shutting down...');
  await closeRedis();
  process.exit(0);
});

process.on('SIGINT', async () => {
  await closeRedis();
  process.exit(0);
});
