import "fake-indexeddb/auto"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { enqueueWrite, flushQueue, listQueue, queueCount } from "./offline-queue"

// Unik matchId per test -- IndexedDB-databasen deles på tvers av tester i
// samme fil (ingen reset mellom tester), men listQueue/flushQueue filtrerer
// alltid på matchId, så unike ID-er isolerer testene fra hverandre helt uten
// å måtte slette/gjenopprette databasen for hver test.
function uniqueMatchId(): string {
  return `match-${Math.random().toString(36).slice(2)}`
}

function jsonResponse(body: unknown, ok = true, status = ok ? 200 : 409): Response {
  return {
    ok,
    status,
    json: async () => body,
  } as Response
}

describe("enqueueWrite / listQueue / queueCount", () => {
  it("lagrer og henter en oppføring for riktig matchId", async () => {
    const matchId = uniqueMatchId()
    await enqueueWrite({ url: "/x", method: "POST", body: { a: 1 }, matchId })
    const entries = await listQueue(matchId)
    expect(entries).toHaveLength(1)
    expect(entries[0].body).toEqual({ a: 1 })
    expect(await queueCount(matchId)).toBe(1)
  })

  it("holder køer for ulike matchId helt adskilt", async () => {
    const matchA = uniqueMatchId()
    const matchB = uniqueMatchId()
    await enqueueWrite({ url: "/x", method: "POST", body: {}, matchId: matchA })
    expect(await queueCount(matchA)).toBe(1)
    expect(await queueCount(matchB)).toBe(0)
  })

  it("bevarer rekkefølgen entries ble lagt til i", async () => {
    const matchId = uniqueMatchId()
    await enqueueWrite({ url: "/x", method: "POST", body: { n: 1 }, matchId })
    await enqueueWrite({ url: "/x", method: "POST", body: { n: 2 }, matchId })
    await enqueueWrite({ url: "/x", method: "POST", body: { n: 3 }, matchId })
    const entries = await listQueue(matchId)
    expect(entries.map((e) => (e.body as { n: number }).n)).toEqual([1, 2, 3])
  })
})

describe("flushQueue -- versjonskjeding per resourceKey (ADR-060 regresjonsvern)", () => {
  beforeEach(() => {
    vi.restoreAllMocks()
  })

  it("kjeder expected_version fremover for FLERE skrivinger til SAMME resourceKey", async () => {
    const matchId = uniqueMatchId()
    await enqueueWrite({
      url: "/hole-scores",
      method: "POST",
      body: { hole_number: 3, expected_version: 1 },
      matchId,
      resourceKey: "hull-3",
    })
    await enqueueWrite({
      url: "/hole-scores",
      method: "POST",
      body: { hole_number: 3, expected_version: 1 }, // fortsatt "1" -- lokal state ble aldri oppdatert offline
      matchId,
      resourceKey: "hull-3",
    })

    const bodiesSent: unknown[] = []
    vi.stubGlobal(
      "fetch",
      vi.fn(async (_url: string, init: RequestInit) => {
        const body = JSON.parse(init.body as string)
        bodiesSent.push(body)
        return jsonResponse({ version: body.expected_version === 1 ? 2 : 3 })
      }),
    )

    const outcomes = await flushQueue(matchId)
    expect(outcomes.every((o) => o.ok)).toBe(true)
    // Første sendt med versjon 1 (som lagret), andre skal ha blitt
    // OVERSTYRT til 2 (versjonen serveren returnerte fra FØRSTE skrivning i
    // denne batchen) -- ikke den opprinnelig lagrede (utdaterte) "1".
    expect((bodiesSent[0] as { expected_version: number }).expected_version).toBe(1)
    expect((bodiesSent[1] as { expected_version: number }).expected_version).toBe(2)
  })

  it("blander IKKE sammen versjon mellom to ULIKE resourceKey som deler samme URL", async () => {
    // Dette er nøyaktig regresjonen som ble funnet og rettet i ADR-060:
    // /hole-scores har samme URL for alle hull i en match. Uten korrekt
    // nøkling ville hull 7 sin skrivning feilaktig arvet versjonen fra
    // hull 3 sin skrivning i samme flush-runde.
    const matchId = uniqueMatchId()
    await enqueueWrite({
      url: "/hole-scores",
      method: "POST",
      body: { hole_number: 3, expected_version: 1 },
      matchId,
      resourceKey: "hull-3",
    })
    await enqueueWrite({
      url: "/hole-scores",
      method: "POST",
      body: { hole_number: 7, expected_version: 5 },
      matchId,
      resourceKey: "hull-7",
    })

    const bodiesSent: unknown[] = []
    vi.stubGlobal(
      "fetch",
      vi.fn(async (_url: string, init: RequestInit) => {
        const body = JSON.parse(init.body as string)
        bodiesSent.push(body)
        return jsonResponse({ version: 99 }) // hull 3 sin server-versjon etter skriving
      }),
    )

    await flushQueue(matchId)
    // Hull 7 sin expected_version skal FORTSATT være 5 -- IKKE 99 (hull 3
    // sin returnerte versjon), siden de er to forskjellige ressurser.
    expect((bodiesSent[0] as { hole_number: number; expected_version: number }).expected_version).toBe(1)
    expect((bodiesSent[1] as { hole_number: number; expected_version: number }).expected_version).toBe(5)
  })

  it("faller tilbake til url som nøkkel når resourceKey ikke er satt (round_hole-bakoverkompatibilitet)", async () => {
    const matchId = uniqueMatchId()
    await enqueueWrite({
      url: "/rounds/r1/participants/p1/holes/5",
      method: "PATCH",
      body: { expected_version: 1 },
      matchId,
      // ingen resourceKey -- samme mønster som round-detail.tsx bruker i dag
    })
    await enqueueWrite({
      url: "/rounds/r1/participants/p1/holes/5",
      method: "PATCH",
      body: { expected_version: 1 },
      matchId,
    })

    const bodiesSent: unknown[] = []
    vi.stubGlobal(
      "fetch",
      vi.fn(async (_url: string, init: RequestInit) => {
        const body = JSON.parse(init.body as string)
        bodiesSent.push(body)
        return jsonResponse({ version: 2 })
      }),
    )

    await flushQueue(matchId)
    expect((bodiesSent[0] as { expected_version: number }).expected_version).toBe(1)
    expect((bodiesSent[1] as { expected_version: number }).expected_version).toBe(2)
  })

  it("fjerner vellykkede oppføringer fra køen, lar mislykkede stå igjen ved ekte nettverksfeil", async () => {
    const matchId = uniqueMatchId()
    await enqueueWrite({ url: "/x", method: "POST", body: { n: 1 }, matchId, resourceKey: "a" })
    await enqueueWrite({ url: "/x", method: "POST", body: { n: 2 }, matchId, resourceKey: "b" })

    let call = 0
    vi.stubGlobal(
      "fetch",
      vi.fn(async () => {
        call += 1
        if (call === 1) return jsonResponse({ version: 1 })
        throw new Error("offline")
      }),
    )

    const outcomes = await flushQueue(matchId)
    expect(outcomes).toHaveLength(1)
    expect(outcomes[0].ok).toBe(true)
    // Andre oppføringen feilet med en EKTE nettverksfeil -- skal fortsatt
    // ligge i køen til neste forsøk, ikke fjernet.
    expect(await queueCount(matchId)).toBe(1)
  })

  it("fjerner en oppføring ved en DEFINITIV HTTP-feil (f.eks. 409) og rapporterer den", async () => {
    const matchId = uniqueMatchId()
    await enqueueWrite({ url: "/x", method: "POST", body: {}, matchId, resourceKey: "a" })

    vi.stubGlobal(
      "fetch",
      vi.fn(async () =>
        jsonResponse({ detail: { code: "STALE_VERSION", message: "Konflikt" } }, false, 409),
      ),
    )

    const outcomes = await flushQueue(matchId)
    expect(outcomes).toHaveLength(1)
    expect(outcomes[0].ok).toBe(false)
    expect(outcomes[0].message).toBe("Konflikt")
    expect(await queueCount(matchId)).toBe(0)
  })
})
