import 'dotenv/config';
import pool from '../db';
import { buildBookmakerBiasReport, type BookmakerBiasInputRow } from '../services/bookmaker-bias';
import { upsertBookmakerQualitySnapshots } from '../models/bookmaker-quality-snapshot';

type CliArgs = {
  asOfDate: string;
  days: number;
  leagues: string[];
  markets: string[];
  minSamples: number;
  staleLagSeconds: number;
  limit: number;
  apply: boolean;
  json: boolean;
};

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

function parseArgs(argv: string[]): CliArgs {
  const args: CliArgs = {
    asOfDate: getCurrentEtDateKey(),
    days: 30,
    leagues: [],
    markets: [],
    minSamples: 20,
    staleLagSeconds: 180,
    limit: 25,
    apply: false,
    json: false,
  };

  for (let i = 0; i < argv.length; i += 1) {
    const arg = argv[i];
    const next = argv[i + 1];
    if (arg === '--date' || arg === '--as-of-date') args.asOfDate = String(next || args.asOfDate);
    else if (arg === '--days') args.days = Number(next || 30) || 30;
    else if (arg === '--league' || arg === '--leagues') args.leagues = String(next || '').split(',').map((value) => value.trim().toLowerCase()).filter(Boolean);
    else if (arg === '--market' || arg === '--markets') args.markets = String(next || '').split(',').map((value) => value.trim().toLowerCase()).filter(Boolean);
    else if (arg === '--min-samples') args.minSamples = Number(next || 20) || 20;
    else if (arg === '--stale-lag-seconds') args.staleLagSeconds = Number(next || 180) || 180;
    else if (arg === '--limit') args.limit = Number(next || 25) || 25;
    else if (arg === '--apply') args.apply = true;
    else if (arg === '--json') args.json = true;
  }

  return args;
}

function printSummaryRow(prefix: string, row: ReturnType<typeof buildBookmakerBiasReport>['summaries'][number]): void {
  const prob = row.avgAbsProbDeltaPct != null ? `${row.avgAbsProbDeltaPct.toFixed(2)}%` : 'n/a';
  const line = row.avgAbsLineDelta != null ? row.avgAbsLineDelta.toFixed(2) : 'n/a';
  const lag = row.avgFreshnessLagSeconds != null ? `${Math.round(row.avgFreshnessLagSeconds)}s` : 'n/a';
  const stale = row.staleSharePct != null ? `${row.staleSharePct.toFixed(1)}%` : 'n/a';
  const best = row.bestPriceSharePct != null ? `${row.bestPriceSharePct.toFixed(1)}%` : 'n/a';
  console.log(`${prefix} ${row.league} ${row.market} ${row.bookmaker} soft=${row.softScore.toFixed(1)} samples=${row.currentConsensusSamples} absProb=${prob} absLine=${line} lag=${lag} stale=${stale} best=${best} bias=${row.probabilityBiasLabel}/${row.lineBiasLabel}`);
}

async function loadRows(args: CliArgs): Promise<BookmakerBiasInputRow[]> {
  const conditions = [
    `("fetchedAt" AT TIME ZONE 'America/New_York')::date
       BETWEEN ($1::date - ($2::int - 1)) AND $1::date`,
  ];
  const params: any[] = [args.asOfDate, args.days];

  if (args.leagues.length > 0) {
    params.push(args.leagues);
    conditions.push(`LOWER(league) = ANY($${params.length}::text[])`);
  }

  if (args.markets.length > 0) {
    params.push(args.markets);
    conditions.push(`LOWER(market) = ANY($${params.length}::text[])`);
  }

  const { rows } = await pool.query(
    `SELECT league,
            "gameId" AS "gameId",
            "gameDate" AS "gameDate",
            "homeTeam" AS "homeTeam",
            "awayTeam" AS "awayTeam",
            bookmaker,
            market,
            "lineValue" AS "lineValue",
            "homeOdds" AS "homeOdds",
            "awayOdds" AS "awayOdds",
            "overOdds" AS "overOdds",
            "underOdds" AS "underOdds",
            "openingLineValue" AS "openingLineValue",
            "openingHomeOdds" AS "openingHomeOdds",
            "openingAwayOdds" AS "openingAwayOdds",
            "openingOverOdds" AS "openingOverOdds",
            "openingUnderOdds" AS "openingUnderOdds",
            "fetchedAt" AS "fetchedAt"
       FROM "GameOdds"
      WHERE ${conditions.join(' AND ')}`,
    params,
  );

  return rows as BookmakerBiasInputRow[];
}

async function persistSnapshots(args: CliArgs, report: ReturnType<typeof buildBookmakerBiasReport>): Promise<number> {
  return upsertBookmakerQualitySnapshots(
    report.summaries.map((row) => ({
      asOfDate: args.asOfDate,
      lookbackDays: args.days,
      league: row.league,
      market: row.market,
      bookmaker: row.bookmaker,
      softScore: row.softScore,
      snapshotCount: row.snapshotCount,
      sampleCount: row.currentConsensusSamples,
      avgAbsProbDeltaPct: row.avgAbsProbDeltaPct,
      avgSignedProbDeltaPct: row.avgSignedProbDeltaPct,
      avgAbsLineDelta: row.avgAbsLineDelta,
      avgSignedLineDelta: row.avgSignedLineDelta,
      avgFreshnessLagSeconds: row.avgFreshnessLagSeconds,
      staleSharePct: row.staleSharePct,
      bestPriceSharePct: row.bestPriceSharePct,
      openingSampleCount: row.openingConsensusSamples,
      openingAvgAbsProbDeltaPct: row.openingAvgAbsProbDeltaPct,
      openingAvgSignedProbDeltaPct: row.openingAvgSignedProbDeltaPct,
      openingAvgAbsLineDelta: row.openingAvgAbsLineDelta,
      openingAvgSignedLineDelta: row.openingAvgSignedLineDelta,
      probabilityBiasLabel: row.probabilityBiasLabel,
      lineBiasLabel: row.lineBiasLabel,
    })),
  );
}

async function main(): Promise<void> {
  const args = parseArgs(process.argv.slice(2));
  const rows = await loadRows(args);
  const report = buildBookmakerBiasReport(rows, {
    staleLagSeconds: args.staleLagSeconds,
    minSamples: args.minSamples,
  });

  const topSoft = report.summaries.slice(0, Math.max(1, args.limit));
  const strongestPositiveBias = [...report.summaries]
    .filter((row) => row.avgSignedProbDeltaPct != null && row.avgSignedProbDeltaPct > 0)
    .sort((left, right) => (right.avgSignedProbDeltaPct || 0) - (left.avgSignedProbDeltaPct || 0))
    .slice(0, Math.max(1, Math.floor(args.limit / 2)));
  const strongestNegativeBias = [...report.summaries]
    .filter((row) => row.avgSignedProbDeltaPct != null && row.avgSignedProbDeltaPct < 0)
    .sort((left, right) => (left.avgSignedProbDeltaPct || 0) - (right.avgSignedProbDeltaPct || 0))
    .slice(0, Math.max(1, Math.floor(args.limit / 2)));
  const persistedCount = args.apply ? await persistSnapshots(args, report) : 0;

  const output = {
    args,
    meta: report.meta,
    persistedCount,
    topSoft,
    strongestPositiveBias,
    strongestNegativeBias,
  };

  if (args.json) {
    console.log(JSON.stringify(output, null, 2));
    await pool.end();
    return;
  }

  console.log('Bookmaker bias audit');
  console.log(`date=${args.asOfDate} lookback=${args.days}d rows=${report.meta.rowCount} marketBuckets=${report.meta.marketCount} staleLag=${report.meta.staleLagSeconds}s apply=${args.apply}`);
  for (const note of report.meta.notes) {
    console.log(`note: ${note}`);
  }
  if (args.apply) {
    console.log(`persisted: ${persistedCount} snapshot rows`);
  }

  console.log('');
  console.log('Top soft books');
  for (const row of topSoft) {
    printSummaryRow('  -', row);
  }

  console.log('');
  console.log('Positive signed bias');
  for (const row of strongestPositiveBias) {
    printSummaryRow('  -', row);
  }

  console.log('');
  console.log('Negative signed bias');
  for (const row of strongestNegativeBias) {
    printSummaryRow('  -', row);
  }

  await pool.end();
}

main().catch((error) => {
  console.error('[bookmaker-bias-audit] failed:', error);
  pool.end().catch(() => undefined);
  process.exitCode = 1;
});
