import { describe, it, expect } from "vitest";
import {
  formatClockTime,
  calculateTimeAfterPick,
  getTimerColorClass,
} from "../draft-timer";

// ─── formatClockTime ─────────────────────────────────────────────────────────

describe("formatClockTime", () => {
  it("returns --:-- for undefined", () => {
    expect(formatClockTime(undefined)).toBe("--:--");
  });

  it("formats 0 seconds", () => {
    expect(formatClockTime(0)).toBe("0:00");
  });

  it("pads single-digit seconds", () => {
    expect(formatClockTime(5)).toBe("0:05");
    expect(formatClockTime(9)).toBe("0:09");
  });

  it("formats seconds under a minute", () => {
    expect(formatClockTime(30)).toBe("0:30");
    expect(formatClockTime(59)).toBe("0:59");
  });

  it("formats exactly one minute", () => {
    expect(formatClockTime(60)).toBe("1:00");
  });

  it("formats mixed minutes and seconds", () => {
    expect(formatClockTime(90)).toBe("1:30");
    expect(formatClockTime(125)).toBe("2:05");
    expect(formatClockTime(150)).toBe("2:30");
  });

  it("formats the default initial bank time", () => {
    expect(formatClockTime(120)).toBe("2:00");
  });

  it("formats hours when >= 3600 seconds", () => {
    expect(formatClockTime(3600)).toBe("1:00:00");
    expect(formatClockTime(3661)).toBe("1:01:01");
    expect(formatClockTime(7322)).toBe("2:02:02");
  });

  it("pads minutes when hours are present", () => {
    expect(formatClockTime(3605)).toBe("1:00:05");
    expect(formatClockTime(3660)).toBe("1:01:00");
  });
});

// ─── calculateTimeAfterPick ───────────────────────────────────────────────────

describe("calculateTimeAfterPick", () => {
  it("adds increment to a healthy bank", () => {
    expect(calculateTimeAfterPick(90, 30)).toBe(120);
  });

  it("adds increment when bank is exactly zero (timer expired before pick)", () => {
    // Timer ran out but autopick fired — team still earns the increment
    expect(calculateTimeAfterPick(0, 30)).toBe(30);
  });

  it("adds increment to a depleted bank (barely positive)", () => {
    expect(calculateTimeAfterPick(3, 30)).toBe(33);
  });

  it("accumulates correctly across multiple quick picks", () => {
    const increment = 30;
    let bank = 120; // initialTime

    // Round 1 — uses 10s, picks fast
    bank -= 10;
    bank = calculateTimeAfterPick(bank, increment); // 140

    // Round 2 — uses 20s
    bank -= 20;
    bank = calculateTimeAfterPick(bank, increment); // 150

    // Round 3 — uses 5s
    bank -= 5;
    bank = calculateTimeAfterPick(bank, increment); // 175

    expect(bank).toBe(175);
  });

  it("depletes over many slow picks", () => {
    const increment = 30;
    let bank = 120;

    // Each round the team uses more time than the increment replenishes
    for (let round = 0; round < 5; round++) {
      bank -= 50; // uses 50s each turn (net loss of 20s per pick)
      bank = calculateTimeAfterPick(bank, increment);
    }

    // Started at 120, lost 20s net × 5 rounds = 20s remaining
    expect(bank).toBe(20);
  });

  it("works with a non-default increment value", () => {
    expect(calculateTimeAfterPick(60, 15)).toBe(75);
    expect(calculateTimeAfterPick(60, 60)).toBe(120);
  });
});

// ─── getTimerColorClass ───────────────────────────────────────────────────────

describe("getTimerColorClass", () => {
  it("returns muted for undefined (clock not running)", () => {
    expect(getTimerColorClass(undefined)).toBe("text-muted-foreground");
  });

  it("returns green for time well above the warning threshold (> 60s)", () => {
    expect(getTimerColorClass(120)).toBe("text-emerald-400");
    expect(getTimerColorClass(61)).toBe("text-emerald-400");
  });

  it("returns amber at the 60-second boundary", () => {
    // 60 is NOT > 60, so it falls to amber
    expect(getTimerColorClass(60)).toBe("text-amber-accent");
  });

  it("returns amber for time between 31–60s", () => {
    expect(getTimerColorClass(45)).toBe("text-amber-accent");
    expect(getTimerColorClass(31)).toBe("text-amber-accent");
  });

  it("returns coral at the 30-second boundary", () => {
    expect(getTimerColorClass(30)).toBe("text-coral-accent");
  });

  it("returns coral for time between 11–30s", () => {
    expect(getTimerColorClass(20)).toBe("text-coral-accent");
    expect(getTimerColorClass(11)).toBe("text-coral-accent");
  });

  it("returns coral+pulse at the 10-second boundary", () => {
    expect(getTimerColorClass(10)).toContain("text-coral-accent");
    expect(getTimerColorClass(10)).toContain("animate-pulse");
  });

  it("returns coral+pulse for critical time (≤ 10s)", () => {
    expect(getTimerColorClass(5)).toContain("animate-pulse");
    expect(getTimerColorClass(1)).toContain("animate-pulse");
    expect(getTimerColorClass(0)).toContain("animate-pulse");
  });

  it("never returns animate-pulse above the critical threshold", () => {
    expect(getTimerColorClass(11)).not.toContain("animate-pulse");
    expect(getTimerColorClass(30)).not.toContain("animate-pulse");
    expect(getTimerColorClass(120)).not.toContain("animate-pulse");
  });
});

// ─── Draft clock lifecycle scenarios ─────────────────────────────────────────
//
// These tests document the intended chess-clock behaviour end-to-end and guard
// against the specific regressions we've hit:
//
//   REGRESSION 1 — Timer reset: next team's bank was being reset to initialTime
//                  instead of left untouched.
//   REGRESSION 2 — Double increment: increment was added both at pick-start AND
//                  after the pick, giving teams 2× the intended bonus.

describe("Draft clock lifecycle", () => {
  const INITIAL_TIME = 120;
  const INCREMENT = 30;

  it("every team starts with exactly initialTime — no increment added upfront", () => {
    // draft.start.ts sets timeRemaining: initialTime (not initialTime + increment)
    const startingBank = INITIAL_TIME;
    expect(startingBank).toBe(120);
    expect(startingBank).not.toBe(INITIAL_TIME + INCREMENT);
  });

  it("REGRESSION 1 — next team's bank is preserved when another team picks", () => {
    // Team B has been on the clock and has 75s remaining.
    // Team A (not team B) makes a pick.
    // Team B's bank must NOT be touched at all.
    const teamBBank = 75;

    // Verify that calling calculateTimeAfterPick on team B WOULD change the value —
    // this proves that a mutation is detectable and the correct behaviour is to skip it.
    const incorrectlyUpdated = calculateTimeAfterPick(teamBBank, INCREMENT);
    expect(incorrectlyUpdated).not.toBe(teamBBank); // 105 ≠ 75: mutation would be visible

    // Correct behaviour: team B's bank is left unchanged (no calculateTimeAfterPick called)
    expect(teamBBank).toBe(75);
    expect(teamBBank).not.toBe(INITIAL_TIME); // 75 ≠ 120: not reset to initialTime (old bug)
  });

  it("REGRESSION 2 — increment is added exactly once per pick (post-pick only)", () => {
    // Team A is on the clock. They have 90s and use 30s before picking.
    let teamABank = 90;
    teamABank -= 30; // 60s left at pick time

    // Post-pick reward: +increment once
    teamABank = calculateTimeAfterPick(teamABank, INCREMENT);

    expect(teamABank).toBe(90); // 60 + 30 = 90  (not 120, which would be double-increment)
  });

  it("full two-team, three-round snake draft simulation", () => {
    // Settings
    const init = INITIAL_TIME; // 120s
    const inc = INCREMENT;     // 30s

    let teamA = init; // 120
    let teamB = init; // 120

    // ── Round 1 ──────────────────────────
    // Pick 1: Team A on clock, uses 20s
    teamA -= 20;                                    // 100
    teamA = calculateTimeAfterPick(teamA, inc);     // 130
    // teamB untouched: 120

    // Pick 2: Team B on clock, uses 60s
    teamB -= 60;                                    // 60
    teamB = calculateTimeAfterPick(teamB, inc);     // 90
    // teamA untouched: 130

    // ── Round 2 (reversed) ───────────────
    // Pick 3: Team B on clock, uses 40s
    teamB -= 40;                                    // 50
    teamB = calculateTimeAfterPick(teamB, inc);     // 80
    // teamA untouched: 130

    // Pick 4: Team A on clock, uses 10s
    teamA -= 10;                                    // 120
    teamA = calculateTimeAfterPick(teamA, inc);     // 150
    // teamB untouched: 80

    // ── Round 3 ──────────────────────────
    // Pick 5: Team A on clock, uses 80s
    teamA -= 80;                                    // 70
    teamA = calculateTimeAfterPick(teamA, inc);     // 100
    // teamB untouched: 80

    // Pick 6: Team B on clock, uses 75s
    teamB -= 75;                                    // 5
    teamB = calculateTimeAfterPick(teamB, inc);     // 35
    // teamA untouched: 100

    expect(teamA).toBe(100); // quick picker — healthy bank
    expect(teamB).toBe(35);  // slow picker — barely surviving
  });

  it("team that consistently runs out of time survives on increments alone", () => {
    let bank = INITIAL_TIME; // 120

    for (let round = 0; round < 4; round++) {
      // Use the entire bank each round (timer hits 0 before pick fires)
      bank = 0;
      bank = calculateTimeAfterPick(bank, INCREMENT);
    }

    // After 4 rounds of running the clock dry, bank = just the increment
    expect(bank).toBe(INCREMENT); // 30s — still alive, just barely
  });
});
