import 'dotenv/config';
import pool from '../db';
import { buildForecastFromCuratedEvent } from '../services/forecast-builder';
import {
  extractMlbDirectFeatures,
  MLB_DIRECT_MODEL_LIVE_CONFIG,
  MLB_DIRECT_MODEL_LIVE_POLICY,
  scoreMlbDirectModel,
} from '../services/mlb-team-direct-model';
import type { SignalResult } from '../services/rie/types';

function getArg(flag: string): string | null {
  const idx = process.argv.indexOf(flag);
  return idx >= 0 && idx + 1 < process.argv.length ? process.argv[idx + 1] : null;
}

function etDateKey(date = new Date()): string {
  return new Intl.DateTimeFormat('en-CA', {
    timeZone: 'America/New_York',
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  }).format(date);
}

type CachedRow = {
  event_id: string;
  league: string;
  home_team: string;
  away_team: string;
  starts_at: string;
  odds_data: any;
  forecast_data: any;
  model_signals: any;
  composite_confidence: number | null;
  confidence_score: number | null;
};

type CuratedRow = {
  event_id: string;
  league: string;
  home_team: string;
  away_team: string;
  home_short: string | null;
  away_short: string | null;
  starts_at: string;
  moneyline: any;
  spread: any;
  total: any;
};

type DriftRow = {
  eventId: string;
  startsAt: string;
  cachedPath: 'mlb_direct_model' | 'mlb_baseline_fallback';
  currentPolicyPath: 'mlb_direct_model' | 'mlb_baseline_fallback';
  cachedWinnerPick: string | null;
  directWinnerPick: string | null;
  directConfidence: number | null;
  reasons: string[];
};

function normalizeSignals(modelSignals: any): SignalResult[] {
  const payload = typeof modelSignals === 'string' ? JSON.parse(modelSignals) : (modelSignals || {});
  return Array.isArray(payload?.rieSignals) ? payload.rieSignals : [];
}

async function loadCachedRows(dateKey: string, eventId?: string | null): Promise<CachedRow[]> {
  const params: any[] = [dateKey];
  let sql = `SELECT event_id, league, home_team, away_team, starts_at,
                    odds_data, forecast_data, model_signals,
                    composite_confidence, confidence_score
               FROM rm_forecast_cache
              WHERE league = 'mlb'
                AND (starts_at AT TIME ZONE 'America/New_York')::date = $1::date`;
  if (eventId) {
    params.push(eventId);
    sql += ` AND event_id = $${params.length}`;
  }
  sql += ' ORDER BY starts_at ASC, event_id ASC';
  const { rows } = await pool.query(sql, params);
  return rows as CachedRow[];
}

async function loadCuratedRows(eventIds: string[]): Promise<Map<string, CuratedRow>> {
  if (!eventIds.length) return new Map();
  const { rows } = await pool.query(
    `SELECT event_id, league, home_team, away_team, home_short, away_short,
            starts_at, moneyline, spread, total
       FROM rm_events
      WHERE event_id = ANY($1::text[])`,
    [eventIds],
  );
  return new Map((rows as CuratedRow[]).map((row) => [row.event_id, row]));
}

function detectDrift(row: CachedRow): DriftRow | null {
  const forecastData = typeof row.forecast_data === 'string' ? JSON.parse(row.forecast_data) : (row.forecast_data || {});
  const signals = normalizeSignals(row.model_signals);
  const featureVector = extractMlbDirectFeatures({
    signals,
    oddsData: row.odds_data || {},
  });
  const decision = scoreMlbDirectModel({
    homeTeam: row.home_team,
    awayTeam: row.away_team,
    featureVector,
    config: MLB_DIRECT_MODEL_LIVE_CONFIG,
    policy: MLB_DIRECT_MODEL_LIVE_POLICY,
  });

  const cachedPath: DriftRow['cachedPath'] = forecastData?.mlb_direct_model?.applied
    ? 'mlb_direct_model'
    : 'mlb_baseline_fallback';
  const currentPolicyPath: DriftRow['currentPolicyPath'] = decision.available && decision.policy.shouldBet && decision.policy.recommendedMarket === 'moneyline'
    ? 'mlb_direct_model'
    : 'mlb_baseline_fallback';

  if (cachedPath === currentPolicyPath) return null;

  return {
    eventId: row.event_id,
    startsAt: row.starts_at,
    cachedPath,
    currentPolicyPath,
    cachedWinnerPick: forecastData?.winner_pick || null,
    directWinnerPick: decision.available ? decision.winnerPick : null,
    directConfidence: decision.available ? decision.confidence : null,
    reasons: decision.policy.reasons,
  };
}

async function main() {
  const dateKey = getArg('--date') || etDateKey();
  const eventId = getArg('--event');
  const apply = process.argv.includes('--apply');
  const limitArg = getArg('--limit');
  const limit = limitArg ? Math.max(1, parseInt(limitArg, 10) || 0) : null;

  const cachedRows = await loadCachedRows(dateKey, eventId);
  const driftRows = cachedRows.map(detectDrift).filter((row): row is DriftRow => Boolean(row));
  const targetRows = limit != null ? driftRows.slice(0, limit) : driftRows;

  console.log(`[mlb-team-refresh-drift] date=${dateKey} cached=${cachedRows.length} drift=${driftRows.length} apply=${apply}`);
  if (!targetRows.length) {
    await pool.end();
    return;
  }

  for (const row of targetRows) {
    const reasonText = row.reasons.length ? ` reasons=${row.reasons.join(' | ')}` : '';
    console.log(`[mlb-team-refresh-drift] drift ${row.eventId} cached=${row.cachedPath} current=${row.currentPolicyPath} cached_pick=${row.cachedWinnerPick || 'null'} direct_pick=${row.directWinnerPick || 'null'} direct_conf=${row.directConfidence ?? 'null'}${reasonText}`);
  }

  if (!apply) {
    await pool.end();
    return;
  }

  const curatedMap = await loadCuratedRows(targetRows.map((row) => row.eventId));
  let refreshed = 0;
  let failed = 0;
  for (const row of targetRows) {
    const curated = curatedMap.get(row.eventId);
    if (!curated) {
      failed += 1;
      console.error(`[mlb-team-refresh-drift] missing rm_events row for ${row.eventId}`);
      continue;
    }
    try {
      const result = await buildForecastFromCuratedEvent(curated, { ignoreCache: true });
      refreshed += 1;
      console.log(`[mlb-team-refresh-drift] rebuilt ${row.eventId} -> ${result.forecastId}`);
    } catch (err: any) {
      failed += 1;
      console.error(`[mlb-team-refresh-drift] failed ${row.eventId}: ${err?.message || err}`);
    }
  }

  console.log(`[mlb-team-refresh-drift] complete refreshed=${refreshed} failed=${failed}`);
  await pool.end();
  process.exit(failed > 0 ? 1 : 0);
}

main().catch(async (error) => {
  console.error('[mlb-team-refresh-drift] fatal', error);
  await pool.end().catch(() => undefined);
  process.exit(1);
});
