import { describe, expect, test } from "bun:test"

import { at, fateOf, hashOf, planFor, type Fate } from "@/lib/demo-plan"

/**
 * The seed's own guarantees, asked without a database.
 *
 * Two of these are the reason the module exists at all. The plan must never
 * name `missed`, because `missed` is the sweep's answer and not an act
 * anybody performs; and the instants must be ordered, because a checked-at
 * before a finished-at is exactly the impossible state the rewrite was for.
 */

const KEYS = Array.from(
  { length: 400 },
  (_, i) =>
    `Wax|Line Leader Wax Injection|Count the trees sent to Casting|2026-07-${String((i % 28) + 1).padStart(2, "0")}#${i}`,
)

describe("hashOf", () => {
  test("is stable, so two runs show the same month", () => {
    expect(hashOf("Accounting|Officer|Prepare the VAT return|2026-08-12")).toBe(
      hashOf("Accounting|Officer|Prepare the VAT return|2026-08-12"),
    )
  })

  test("separates keys that differ only in the day", () => {
    const a = hashOf("x|y|z|2026-08-01")
    const b = hashOf("x|y|z|2026-08-02")
    expect(a).not.toBe(b)
  })

  test("spreads over the hundred buckets the mixture is written in", () => {
    const seen = new Set(KEYS.map((k) => hashOf(k) % 100))
    // Four hundred keys into a hundred buckets: anything under half is a hash
    // clustering, not a coincidence.
    expect(seen.size).toBeGreaterThan(50)
  })
})

describe("fateOf", () => {
  test("never invents an audit for a duty that names no verifier", () => {
    for (const key of KEYS) expect(fateOf(key, false)).not.toBe("audited")
  })

  test("appointing a verifier gets a real share of the work sampled", () => {
    const audited = KEYS.filter((k) => fateOf(k, true) === "audited").length
    expect(audited).toBeGreaterThan(KEYS.length / 10)
  })

  test("every fate in the mixture actually occurs", () => {
    const seen = new Set(KEYS.map((k) => fateOf(k, true)))
    const all: Fate[] = [
      "untouched",
      "stuck",
      "flagged",
      "handed_in",
      "approved",
      "audited",
      "returned",
    ]
    for (const fate of all) expect(seen).toContain(fate)
  })
})

describe("at", () => {
  test("reads a day as a Bangkok wall clock, not as the machine's", () => {
    // 08:30 in Bangkok is 01:30 UTC, whatever the process thinks the time is.
    expect(at("2026-08-05", 8 * 60 + 30).toISOString()).toBe(
      "2026-08-05T01:30:00.000Z",
    )
  })
})

describe("planFor", () => {
  test("names no status, ever: `missed` is the sweep's answer and not an act", () => {
    const verbs = new Set(
      KEYS.flatMap((k) =>
        planFor(k, "2026-07-15", true).acts.map((a) => a.verb),
      ),
    )
    expect([...verbs].sort()).toEqual([
      "approve",
      "finish",
      "report_issue",
      "send_back",
      "start",
      "verify",
    ])
  })

  test("the acts of one task run forward in time, never back", () => {
    for (const key of KEYS) {
      const { acts } = planFor(key, "2026-07-15", true)
      for (let i = 1; i < acts.length; i++) {
        expect(acts[i]!.when.getTime()).toBeGreaterThan(
          acts[i - 1]!.when.getTime(),
        )
        expect(acts[i]!.day >= acts[i - 1]!.day).toBe(true)
      }
    }
  })

  test("nothing happens before the day the work was due", () => {
    for (const key of KEYS) {
      for (const act of planFor(key, "2026-07-15", true).acts) {
        expect(act.day >= "2026-07-15").toBe(true)
      }
    }
  })

  test("work always begins by being started", () => {
    for (const key of KEYS) {
      const { fate, acts } = planFor(key, "2026-07-15", true)
      if (fate === "untouched") expect(acts).toEqual([])
      else expect(acts[0]!.verb).toBe("start")
    }
  })

  test("only a duty with a verifier is ever verified", () => {
    for (const key of KEYS) {
      const acts = planFor(key, "2026-07-15", false).acts
      expect(acts.some((a) => a.verb === "verify")).toBe(false)
    }
  })

  test("an approval is never before the hand-in it approves", () => {
    for (const key of KEYS) {
      const { acts } = planFor(key, "2026-07-15", true)
      const finished = acts.find((a) => a.verb === "finish")
      const approved = acts.find((a) => a.verb === "approve")
      if (finished && approved) {
        expect(approved.when.getTime()).toBeGreaterThan(finished.when.getTime())
      }
    }
  })

  test("the same key gives the same month twice", () => {
    const once = planFor(KEYS[7]!, "2026-07-15", true)
    const twice = planFor(KEYS[7]!, "2026-07-15", true)
    expect(JSON.stringify(once)).toBe(JSON.stringify(twice))
  })

  test("hand-ins are spread across the day rather than sharing one moment", () => {
    const moments = new Set(
      KEYS.map(
        (k) =>
          planFor(k, "2026-07-15", true)
            .acts.find((a) => a.verb === "finish")
            ?.when.getTime() ?? 0,
      ),
    )
    // The old seed had one. Anything in the dozens is a spread.
    expect(moments.size).toBeGreaterThan(40)
  })
})
