import 'dotenv/config';

import pool from '../db';
import { EU_EVENT_LOOKAHEAD_DAYS, isLeagueEventInWindow } from '../lib/league-windows';
import {
  recoverStaleRuns,
  replayTeamPropsForTeam,
  type EventRow,
} from './weather-report';

type Side = 'home' | 'away';

type RepairTargetRow = EventRow & {
  team_side: Side;
  team_short: string | null;
  team_props_count: number;
  player_props_count: number;
};

type RepairScope = {
  dateET: string;
  leagues: string[];
  targetEventId: string | null;
  targetSide: Side | null;
  targetTeamShort: string | null;
  forceReplayAll: boolean;
  useLeagueWindow: boolean;
};

const MIN_PLAYER_PROPS_PER_TEAM = Math.max(1, Number(process.env.MIN_PLAYER_PROPS_PER_TEAM || 5));
const SCHEDULE_NAME = 'PLAYER_PROP_FLOOR_REPAIR';
const DEFAULT_REPAIR_LEAGUES = ['mlb', 'nba', 'nhl', 'epl', 'bundesliga'];

function getTodayET(): string {
  return new Date().toLocaleDateString('en-CA', { timeZone: 'America/New_York' });
}

export function parseRepairScopeFromEnv(env: NodeJS.ProcessEnv = process.env): RepairScope {
  const rawDate = String(env.TARGET_DATE_ET || '').trim();
  const rawLeagues = String(env.TARGET_LEAGUES || env.TARGET_LEAGUE || '').trim();
  const rawSide = String(env.TARGET_SIDE || '').trim().toLowerCase();
  const rawTeamShort = String(env.TARGET_TEAM_SHORT || '').trim().toUpperCase();
  const hasExplicitDate = /^\d{4}-\d{2}-\d{2}$/.test(rawDate);

  return {
    dateET: hasExplicitDate ? rawDate : getTodayET(),
    leagues: rawLeagues
      ? rawLeagues.split(',').map((value) => value.trim().toLowerCase()).filter(Boolean)
      : DEFAULT_REPAIR_LEAGUES,
    targetEventId: String(env.TARGET_EVENT_ID || '').trim() || null,
    targetSide: rawSide === 'home' || rawSide === 'away' ? rawSide : null,
    targetTeamShort: rawTeamShort || null,
    forceReplayAll: String(env.FORCE_REPLAY_ALL || '').trim().toLowerCase() === 'true',
    useLeagueWindow: !hasExplicitDate,
  };
}

export function isRepairTargetInWindow(
  league: string,
  startsAt: string | Date,
  now: Date = new Date(),
): boolean {
  return isLeagueEventInWindow(league, startsAt, EU_EVENT_LOOKAHEAD_DAYS, now, 0);
}

export function shouldReplayTarget(params: {
  forceReplayAll: boolean;
  explicitTarget: boolean;
  teamPropsCount: number;
  playerPropsCount: number;
  minPlayerPropsPerTeam: number;
}): boolean {
  if (params.forceReplayAll || params.explicitTarget) return true;
  if (params.teamPropsCount === 0) return true;
  return params.playerPropsCount < params.minPlayerPropsPerTeam;
}

async function createRun(dateET: string): Promise<string> {
  await recoverStaleRuns(dateET);
  const { rows } = await pool.query(
    `INSERT INTO rm_weather_report_runs
       (date_et, schedule_name, status, total_contests_found, total_forecasts_generated, total_skipped_due_to_cap, total_failures, total_odds_refreshed, duration_ms, error_message, log_blob)
     VALUES ($1, $2, 'RUNNING', 0, 0, 0, 0, 0, 0, NULL, NULL)
     RETURNING id`,
    [dateET, SCHEDULE_NAME],
  );
  return String(rows[0]?.id || '');
}

async function finalizeRun(runId: string, totals: {
  contests: number;
  generated: number;
  failures: number;
  startedAtMs: number;
  log: string[];
  fatal?: string | null;
}) {
  const status = totals.fatal
    ? 'FAILED'
    : totals.failures > 0
      ? 'PARTIAL'
      : 'SUCCESS';
  await pool.query(
    `UPDATE rm_weather_report_runs
     SET status = $2,
         total_contests_found = $3,
         total_forecasts_generated = $4,
         total_failures = $5,
         duration_ms = $6,
         error_message = $7,
         log_blob = $8
     WHERE id = $1`,
    [
      runId,
      status,
      totals.contests,
      totals.generated,
      totals.failures,
      Date.now() - totals.startedAtMs,
      totals.fatal || null,
      totals.log.join('\n').slice(-50000),
    ],
  );
}

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function queryRepairTargets(scope: RepairScope): Promise<RepairTargetRow[]> {
  const params: any[] = [scope.dateET, scope.leagues];
  if (scope.useLeagueWindow) {
    params.push(Math.max(0, EU_EVENT_LOOKAHEAD_DAYS));
  }
  const eventFilterSql = scope.useLeagueWindow
    ? `e.starts_at > NOW()
          AND e.starts_at < NOW() + (($3::int + 1) * INTERVAL '1 day')
          AND e.status = 'scheduled'
          AND LOWER(e.league) = ANY($2::text[])`
    : `(e.starts_at AT TIME ZONE 'America/New_York')::date = $1::date
          AND LOWER(e.league) = ANY($2::text[])`;
  const filters: string[] = [];

  if (scope.targetEventId) {
    params.push(scope.targetEventId);
    filters.push(`targets.event_id = $${params.length}`);
  }
  if (scope.targetSide) {
    params.push(scope.targetSide);
    filters.push(`targets.team_side = $${params.length}`);
  }
  if (scope.targetTeamShort) {
    params.push(scope.targetTeamShort);
    filters.push(`COALESCE(targets.team_short, '') = $${params.length}`);
  }

  const explicitTarget = Boolean(scope.targetEventId || scope.targetSide || scope.targetTeamShort);
  let thresholdClause = 'TRUE';
  if (!scope.forceReplayAll && !explicitTarget) {
    params.push(MIN_PLAYER_PROPS_PER_TEAM);
    thresholdClause = `(targets.team_props_count = 0 OR targets.player_props_count < $${params.length})`;
  }

  const whereClause = filters.length > 0
    ? `WHERE ${filters.join(' AND ')} AND ${thresholdClause}`
    : `WHERE ${thresholdClause}`;

  const { rows } = await pool.query(
    `WITH targets AS (
       SELECT e.event_id, e.league, e.home_team, e.away_team, e.home_short, e.away_short,
              e.starts_at, e.moneyline, e.spread, e.total, COALESCE(e.prop_count, 0) AS prop_count,
              'home'::text AS team_side,
              e.home_short AS team_short,
              COALESCE(stats.home_team_props_count, 0)::int AS team_props_count,
              COALESCE(stats.home_player_props_count, 0)::int AS player_props_count
       FROM rm_events e
         LEFT JOIN LATERAL (
           SELECT COUNT(*) FILTER (WHERE fp.forecast_type = 'TEAM_PROPS' AND fp.team_side = 'home') AS home_team_props_count,
                  COUNT(*) FILTER (WHERE fp.forecast_type = 'TEAM_PROPS' AND fp.team_side = 'away') AS away_team_props_count,
                  COUNT(*) FILTER (WHERE fp.forecast_type = 'PLAYER_PROP' AND fp.team_side = 'home') AS home_player_props_count,
                  COUNT(*) FILTER (WHERE fp.forecast_type = 'PLAYER_PROP' AND fp.team_side = 'away') AS away_player_props_count
             FROM rm_forecast_precomputed fp
            WHERE fp.date_et = $1
              AND fp.event_id = e.event_id
       ) stats ON true
        WHERE ${eventFilterSql}
       UNION ALL
       SELECT e.event_id, e.league, e.home_team, e.away_team, e.home_short, e.away_short,
              e.starts_at, e.moneyline, e.spread, e.total, COALESCE(e.prop_count, 0) AS prop_count,
              'away'::text AS team_side,
              e.away_short AS team_short,
              COALESCE(stats.away_team_props_count, 0)::int AS team_props_count,
              COALESCE(stats.away_player_props_count, 0)::int AS player_props_count
         FROM rm_events e
         LEFT JOIN LATERAL (
           SELECT COUNT(*) FILTER (WHERE fp.forecast_type = 'TEAM_PROPS' AND fp.team_side = 'home') AS home_team_props_count,
                  COUNT(*) FILTER (WHERE fp.forecast_type = 'TEAM_PROPS' AND fp.team_side = 'away') AS away_team_props_count,
                  COUNT(*) FILTER (WHERE fp.forecast_type = 'PLAYER_PROP' AND fp.team_side = 'home') AS home_player_props_count,
                  COUNT(*) FILTER (WHERE fp.forecast_type = 'PLAYER_PROP' AND fp.team_side = 'away') AS away_player_props_count
             FROM rm_forecast_precomputed fp
            WHERE fp.date_et = $1
              AND fp.event_id = e.event_id
       ) stats ON true
        WHERE ${eventFilterSql}
     )
     SELECT *
       FROM targets
       ${whereClause}
      ORDER BY starts_at ASC, event_id ASC, team_side ASC`,
    params,
  );

  const filteredRows = scope.useLeagueWindow
    ? rows.filter((row: any) => isRepairTargetInWindow(row.league, row.starts_at))
    : rows;

  return filteredRows as RepairTargetRow[];
}

export async function main(): Promise<number> {
  const startedAtMs = Date.now();
  const scope = parseRepairScopeFromEnv();
  const runId = await createRun(scope.dateET);
  const log: string[] = [];
  let generated = 0;
  let failures = 0;

  try {
    const targets = await queryRepairTargets(scope);
    log.push(`[repair] schedule=${SCHEDULE_NAME} date=${scope.dateET} window=${scope.useLeagueWindow ? `league:${EU_EVENT_LOOKAHEAD_DAYS}d` : 'explicit-date'} targets=${targets.length} leagues=${scope.leagues.join(',')}`);
    console.log(log[log.length - 1]);

    for (let index = 0; index < targets.length; index++) {
      const target = targets[index];
      try {
        console.log(`[repair] [${index + 1}/${targets.length}] replay ${target.event_id} ${target.team_side} (${target.team_short || 'UNK'}) team_props=${target.team_props_count} player_props=${target.player_props_count}`);
        const replayed = await replayTeamPropsForTeam(
          target,
          target.team_side,
          scope.dateET,
          runId,
          null,
        );
        generated += replayed.assetWrites;
        const diagnostics = replayed.diagnostics;
        const summary = [
          `[repair] ${target.event_id} ${target.team_side}`,
          `staled=${replayed.staleWrites}`,
          `raw=${diagnostics.rawPropCount}`,
          `filtered=${diagnostics.filteredPropCount}`,
          `publishable=${diagnostics.publishablePropCount}`,
          `stored=${diagnostics.playerPropsStored}`,
          `drop_no_pricing=${diagnostics.droppedMissingPricing}`,
          `drop_non_roster=${diagnostics.droppedNonRoster}`,
          `store_failures=${diagnostics.storeFailures}`,
        ].join(' ');
        log.push(summary);
        console.log(summary);
      } catch (err: any) {
        failures++;
        const message = `[repair] ${target.event_id} ${target.team_side} failed: ${err?.message || String(err)}`;
        log.push(message);
        console.error(message);
      }

      if (index < targets.length - 1) {
        await sleep(3000);
      }
    }

    await finalizeRun(runId, {
      contests: targets.length,
      generated,
      failures,
      startedAtMs,
      log,
    });
    return failures > 0 ? 1 : 0;
  } catch (err: any) {
    const fatal = String(err?.message || err);
    log.push(`[repair] fatal: ${fatal}`);
    console.error(log[log.length - 1]);
    await finalizeRun(runId, {
      contests: 0,
      generated,
      failures: failures + 1,
      startedAtMs,
      log,
      fatal,
    });
    throw err;
  } finally {
    await pool.end().catch(() => undefined);
  }
}

if (require.main === module) {
  main()
    .then((code) => process.exit(code))
    .catch(() => process.exit(1));
}
