/**
 * ===================================================
 * BACKTEST REPORT
 * ===================================================
 * Reads prediction_runs (with eventual_outcome + unit_profit
 * already filled by settlement / backfill) and prints
 * per-engine x per-market ROI tables. Also writes JSON.
 *
 * Usage:
 *   npx ts-node --transpile-only -r tsconfig-paths/register \
 *     src/scripts/print-backtest-report.ts [--engine v28-pro-max]
 */

import { PrismaClient, Prisma } from "@prisma/client";
import * as fs from "fs";
import * as path from "path";

const prisma = new PrismaClient();

interface RawRow {
  engine_version: string;
  market: string | null;
  bet_grade: string | null;
  outcome_kind: "WON" | "LOST" | "NO_BET";
  unit_profit: number | null;
  count: bigint;
}

interface AggregatedBucket {
  engine: string;
  market: string;
  betGrade: string;
  totalBets: number;
  wins: number;
  losses: number;
  noBets: number;
  totalProfit: number;
  roi: number;
  winRate: number;
}

function parseArgs() {
  const args = process.argv.slice(2);
  const opts: Record<string, string> = {};
  for (let i = 0; i < args.length; i += 2) {
    opts[args[i].replace(/^--/, "")] = args[i + 1];
  }
  return { engineFilter: opts.engine ?? null };
}

async function loadRows(engineFilter: string | null): Promise<RawRow[]> {
  const engineClause = engineFilter
    ? Prisma.sql`AND pr.engine_version = ${engineFilter}`
    : Prisma.sql``;

  return prisma.$queryRaw<RawRow[]>(Prisma.sql`
    SELECT pr.engine_version,
           pr.payload_summary->'main_pick'->>'market' AS market,
           pr.payload_summary->'main_pick'->>'bet_grade' AS bet_grade,
           CASE
             WHEN pr.eventual_outcome LIKE 'WON:%' THEN 'WON'
             WHEN pr.eventual_outcome LIKE 'LOST:%' THEN 'LOST'
             ELSE 'NO_BET'
           END AS outcome_kind,
           pr.unit_profit,
           1::bigint AS count
    FROM prediction_runs pr
    WHERE pr.eventual_outcome IS NOT NULL
    ${engineClause}
  `);
}

function aggregate(rows: RawRow[]): AggregatedBucket[] {
  const map = new Map<string, AggregatedBucket>();
  for (const r of rows) {
    const market = r.market ?? "(none)";
    const betGrade = r.bet_grade ?? "(none)";
    const key = `${r.engine_version}|${market}|${betGrade}`;
    if (!map.has(key)) {
      map.set(key, {
        engine: r.engine_version,
        market,
        betGrade,
        totalBets: 0,
        wins: 0,
        losses: 0,
        noBets: 0,
        totalProfit: 0,
        roi: 0,
        winRate: 0,
      });
    }
    const b = map.get(key)!;
    if (r.outcome_kind === "WON") b.wins += 1;
    else if (r.outcome_kind === "LOST") b.losses += 1;
    else b.noBets += 1;
    if (r.outcome_kind !== "NO_BET") b.totalBets += 1;
    b.totalProfit += Number(r.unit_profit ?? 0);
  }

  const out = Array.from(map.values());
  for (const b of out) {
    b.roi = b.totalBets > 0 ? b.totalProfit / b.totalBets : 0;
    b.winRate = b.totalBets > 0 ? b.wins / b.totalBets : 0;
  }
  return out.sort((a, b) => b.totalBets - a.totalBets);
}

function printTable(buckets: AggregatedBucket[]) {
  const header = [
    "engine".padEnd(20),
    "market".padEnd(10),
    "grade".padEnd(6),
    "bets".padStart(7),
    "wins".padStart(7),
    "losses".padStart(7),
    "noBet".padStart(7),
    "winRate".padStart(8),
    "profit".padStart(10),
    "ROI".padStart(8),
  ].join(" │ ");
  console.log(header);
  console.log("─".repeat(header.length));
  for (const b of buckets) {
    console.log(
      [
        b.engine.padEnd(20),
        b.market.padEnd(10),
        b.betGrade.padEnd(6),
        String(b.totalBets).padStart(7),
        String(b.wins).padStart(7),
        String(b.losses).padStart(7),
        String(b.noBets).padStart(7),
        `${(b.winRate * 100).toFixed(1)}%`.padStart(8),
        b.totalProfit.toFixed(2).padStart(10),
        `${(b.roi * 100).toFixed(2)}%`.padStart(8),
      ].join(" │ "),
    );
  }
}

function engineSummary(buckets: AggregatedBucket[]) {
  const byEngine = new Map<string, AggregatedBucket>();
  for (const b of buckets) {
    if (!byEngine.has(b.engine)) {
      byEngine.set(b.engine, {
        engine: b.engine,
        market: "ALL",
        betGrade: "ALL",
        totalBets: 0,
        wins: 0,
        losses: 0,
        noBets: 0,
        totalProfit: 0,
        roi: 0,
        winRate: 0,
      });
    }
    const acc = byEngine.get(b.engine)!;
    acc.totalBets += b.totalBets;
    acc.wins += b.wins;
    acc.losses += b.losses;
    acc.noBets += b.noBets;
    acc.totalProfit += b.totalProfit;
  }
  const out = Array.from(byEngine.values());
  for (const e of out) {
    e.roi = e.totalBets > 0 ? e.totalProfit / e.totalBets : 0;
    e.winRate = e.totalBets > 0 ? e.wins / e.totalBets : 0;
  }
  return out;
}

async function main() {
  const { engineFilter } = parseArgs();
  console.log("📊 Backtest report");
  if (engineFilter) console.log(`   filter engine = ${engineFilter}`);

  const rows = await loadRows(engineFilter);
  console.log(`   ${rows.length} resolved prediction_runs loaded\n`);

  const buckets = aggregate(rows);
  const enginesAll = engineSummary(buckets);

  console.log("=== Per engine (all markets) ===");
  printTable(enginesAll);
  console.log("\n=== Per engine × market × bet_grade ===");
  printTable(buckets);

  const ts = new Date().toISOString().replace(/[:.]/g, "-");
  const outDir = path.join(process.cwd(), "reports");
  if (!fs.existsSync(outDir)) fs.mkdirSync(outDir);
  const outFile = path.join(outDir, `backtest-${ts}.json`);
  fs.writeFileSync(
    outFile,
    JSON.stringify({ engines: enginesAll, buckets }, null, 2),
    "utf8",
  );
  console.log(`\n💾 Saved ${outFile}`);

  await prisma.$disconnect();
}

main().catch(async (err) => {
  console.error(err);
  await prisma.$disconnect();
  process.exit(1);
});
