import { mkdtemp, readFile, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { describe, expect, it } from "bun:test";
import {
  appendHistoryRecord,
  buildRunResult,
  formatRunSummary,
} from "./results";
import type { BenchmarkCaseResult } from "./types";

function caseResult(
  attempts: BenchmarkCaseResult["attempts"],
): BenchmarkCaseResult {
  return {
    id: "case-1",
    prompt: "Do the thing",
    attempts,
  };
}

describe("benchmark results", () => {
  it("keeps success cost metrics separate from failed attempts", () => {
    const result = buildRunResult({
      mode: "global",
      runs: 1,
      runModel: "model-under-test",
      judgeModel: "judge-model",
      caseResults: [
        caseResult([
          {
            attempt: 1,
            passed: true,
            durationMs: 1000,
            assistantMessageCount: 1,
            toolCallCount: 1,
            toolsUsed: ["edit_script"],
            skillsInvoked: [],
            checks: [{ name: "edited", passed: true }],
            judgeScore: 100,
            judgeSummary: "ok",
            error: null,
            tokenUsage: { prompt: 100, completion: 20, total: 120 },
          },
          {
            attempt: 2,
            passed: false,
            durationMs: 100,
            assistantMessageCount: 1,
            toolCallCount: 0,
            toolsUsed: [],
            skillsInvoked: [],
            checks: [{ name: "edited", passed: false }],
            judgeScore: 10,
            judgeSummary: "missed",
            error: "failed",
            tokenUsage: { prompt: 10, completion: 5, total: 15 },
          },
        ]),
      ],
    });

    expect(result.attemptCount).toBe(2);
    expect(result.passedAttempts).toBe(1);
    expect(result.passRate).toBe(0.5);
    expect(result.averageDurationMs).toBe(550);
    expect(result.averagePassedDurationMs).toBe(1000);
    expect(result.totalTokenUsage).toEqual({
      prompt: 110,
      completion: 25,
      total: 135,
    });
    expect(result.totalPassedTokenUsage).toEqual({
      prompt: 100,
      completion: 20,
      total: 120,
    });
    expect(result.averageTokenUsagePerAttempt).toEqual({
      prompt: 55,
      completion: 12.5,
      total: 67.5,
    });
    expect(result.averageTokenUsagePerPassedAttempt).toEqual({
      prompt: 100,
      completion: 20,
      total: 120,
    });

    const summary = formatRunSummary(result);
    expect(summary).toContain("Average duration (passed): 1000ms");
    expect(summary).toContain("Average tokens (passed): 120 total");
    expect(summary).toContain("Average duration (all attempts): 550ms");
  });

  it("aggregates final context size over passed attempts only", () => {
    const result = buildRunResult({
      mode: "global",
      runs: 1,
      runModel: "model-under-test",
      judgeModel: "judge-model",
      caseResults: [
        caseResult([
          {
            attempt: 1,
            passed: true,
            durationMs: 1000,
            assistantMessageCount: 1,
            toolCallCount: 1,
            toolsUsed: ["edit_script"],
            skillsInvoked: [],
            checks: [{ name: "edited", passed: true }],
            judgeScore: 100,
            judgeSummary: "ok",
            error: null,
            tokenUsage: { prompt: 12000, completion: 200, total: 12200 },
            finalContextTokens: 5000,
          },
          {
            attempt: 2,
            passed: true,
            durationMs: 1100,
            assistantMessageCount: 1,
            toolCallCount: 1,
            toolsUsed: ["edit_script"],
            skillsInvoked: [],
            checks: [{ name: "edited", passed: true }],
            judgeScore: 100,
            judgeSummary: "ok",
            error: null,
            tokenUsage: { prompt: 18000, completion: 300, total: 18300 },
            finalContextTokens: 7000,
          },
          {
            attempt: 3,
            passed: false,
            durationMs: 100,
            assistantMessageCount: 1,
            toolCallCount: 0,
            toolsUsed: [],
            skillsInvoked: [],
            checks: [{ name: "edited", passed: false }],
            judgeScore: 10,
            judgeSummary: "missed",
            error: "failed",
            tokenUsage: { prompt: 20000, completion: 100, total: 20100 },
            finalContextTokens: 9000,
          },
        ]),
      ],
    });

    // Final context size stays below cumulative prompt and ignores the failed attempt.
    expect(result.averageFinalContextTokensPassed).toBe(6000);
    expect(result.maxFinalContextTokensPassed).toBe(7000);
    expect(formatRunSummary(result)).toContain(
      "Final context size (passed): 6000 tokens (max 7000)",
    );
  });

  it("reports passed averages as unavailable when no attempt passes", () => {
    const result = buildRunResult({
      mode: "global",
      runs: 1,
      runModel: "model-under-test",
      judgeModel: "judge-model",
      caseResults: [
        caseResult([
          {
            attempt: 1,
            passed: false,
            durationMs: 100,
            assistantMessageCount: 1,
            toolCallCount: 0,
            toolsUsed: [],
            skillsInvoked: [],
            checks: [{ name: "edited", passed: false }],
            judgeScore: 10,
            judgeSummary: "missed",
            error: "failed",
            tokenUsage: { prompt: 10, completion: 5, total: 15 },
          },
        ]),
      ],
    });

    expect(result.averagePassedDurationMs).toBeNull();
    expect(result.totalPassedTokenUsage).toBeNull();
    expect(result.averageTokenUsagePerPassedAttempt).toBeNull();
    expect(formatRunSummary(result)).toContain(
      "Average duration (passed): n/a",
    );
  });

  it("normalizes passed token averages by passed attempts", () => {
    const result = buildRunResult({
      mode: "global",
      runs: 1,
      runModel: "model-under-test",
      judgeModel: "judge-model",
      caseResults: [
        caseResult([
          {
            attempt: 1,
            passed: true,
            durationMs: 1000,
            assistantMessageCount: 1,
            toolCallCount: 1,
            toolsUsed: ["edit_script"],
            skillsInvoked: [],
            checks: [{ name: "edited", passed: true }],
            judgeScore: 100,
            judgeSummary: "ok",
            error: null,
            tokenUsage: { prompt: 100, completion: 20, total: 120 },
          },
          {
            attempt: 2,
            passed: true,
            durationMs: 1200,
            assistantMessageCount: 1,
            toolCallCount: 1,
            toolsUsed: ["edit_script"],
            skillsInvoked: [],
            checks: [{ name: "edited", passed: true }],
            judgeScore: 100,
            judgeSummary: "ok",
            error: null,
            tokenUsage: null,
          },
        ]),
      ],
    });

    expect(result.passedAttempts).toBe(2);
    expect(result.totalPassedTokenUsage).toEqual({
      prompt: 100,
      completion: 20,
      total: 120,
    });
    expect(result.averageTokenUsagePerPassedAttempt).toEqual({
      prompt: 50,
      completion: 10,
      total: 60,
    });
  });

  it("records passed-attempt metrics in history", async () => {
    const tempDir = await mkdtemp(join(tmpdir(), "windmill-ai-evals-"));
    try {
      const historyPath = join(tempDir, "history.jsonl");
      const result = buildRunResult({
        mode: "global",
        runs: 1,
        runModel: "model-under-test",
        judgeModel: "judge-model",
        caseResults: [
          caseResult([
            {
              attempt: 1,
              passed: true,
              durationMs: 1000,
              assistantMessageCount: 1,
              toolCallCount: 1,
              toolsUsed: ["edit_script"],
              skillsInvoked: [],
              checks: [{ name: "edited", passed: true }],
              judgeScore: 100,
              judgeSummary: "ok",
              error: null,
              tokenUsage: { prompt: 100, completion: 20, total: 120 },
            },
            {
              attempt: 2,
              passed: false,
              durationMs: 100,
              assistantMessageCount: 1,
              toolCallCount: 0,
              toolsUsed: [],
              skillsInvoked: [],
              checks: [{ name: "edited", passed: false }],
              judgeScore: 10,
              judgeSummary: "missed",
              error: "failed",
              tokenUsage: { prompt: 10, completion: 5, total: 15 },
            },
          ]),
        ],
      });

      await appendHistoryRecord(result, historyPath);
      const record = JSON.parse(await readFile(historyPath, "utf8"));

      expect(record.averageDurationMs).toBe(550);
      expect(record.averagePassedDurationMs).toBe(1000);
      expect(record.averageTokenUsagePerAttempt.total).toBe(67.5);
      expect(record.averageTokenUsagePerPassedAttempt.total).toBe(120);
      expect(record.cases[0].averageDurationMs).toBe(550);
      expect(record.cases[0].averagePassedDurationMs).toBe(1000);
      expect(record.cases[0].averageTokenUsagePerAttempt.total).toBe(67.5);
      expect(record.cases[0].averageTokenUsagePerPassedAttempt.total).toBe(
        120,
      );
    } finally {
      await rm(tempDir, { recursive: true, force: true });
    }
  });
});
