// @vitest-environment jsdom
// src/lib/offline/queue.test.ts
import { describe, it, expect, beforeEach } from "vitest";
import "fake-indexeddb/auto";
import {
  enqueueAction,
  dequeueAction,
  getQueuedActions,
  markSynced,
  markConflict,
  markInFlight,
  markFailed,
} from "./queue";

beforeEach(async () => {
  // Clear the queue between tests
  const { getOfflineDB } = await import("./db");
  const db = await getOfflineDB();
  await db.clear("queue");
});

describe("offline queue", () => {
  it("enqueueAction stores an action with a generated clientActionId", async () => {
    const action = await enqueueAction("markOrderReady", { orderId: "abc" });
    expect(action.id).toMatch(/^[0-9a-f-]{36}$/i);
    expect(action.actionName).toBe("markOrderReady");
    expect(action.status).toBe("pending");
    expect(action.attempts).toBe(0);
  });

  it("enqueueAction is idempotent on the same clientActionId", async () => {
    const a = await enqueueAction("markOrderReady", { orderId: "abc" }, { id: "fixed-id" });
    const b = await enqueueAction("markOrderReady", { orderId: "abc" }, { id: "fixed-id" });
    expect(a.id).toBe(b.id);
    expect(a.id).toBe("fixed-id");
    const all = await getQueuedActions();
    expect(all).toHaveLength(1);
  });

  it("getQueuedActions returns pending actions in createdAt order", async () => {
    const a = await enqueueAction("a", {}, { id: "a" });
    // Small delay to ensure ordering
    await new Promise((r) => setTimeout(r, 5));
    const b = await enqueueAction("b", {}, { id: "b" });
    const all = await getQueuedActions();
    expect(all.map((x) => x.id)).toEqual([a.id, b.id]);
  });

  it("dequeueAction removes the action and returns its data", async () => {
    const a = await enqueueAction("markOrderReady", { orderId: "abc" });
    const removed = await dequeueAction(a.id);
    expect(removed?.id).toBe(a.id);
    const all = await getQueuedActions();
    expect(all).toHaveLength(0);
  });

  it("markSynced removes the action", async () => {
    const a = await enqueueAction("markOrderReady", { orderId: "abc" });
    await markSynced(a.id);
    const all = await getQueuedActions();
    expect(all).toHaveLength(0);
  });

  it("markConflict moves the action to conflict status", async () => {
    const a = await enqueueAction("markOrderReady", { orderId: "abc" });
    await markConflict(a.id, "stale updated_at");
    const all = await getQueuedActions();
    expect(all).toHaveLength(1);
    expect(all[0].status).toBe("conflict");
    expect(all[0].error).toBe("stale updated_at");
  });

  it("markInFlight transitions the action to in-flight and records an attempt", async () => {
    const a = await enqueueAction("markOrderReady", { orderId: "abc" });
    const before = await getQueuedActions();
    expect(before[0].attempts).toBe(0);
    expect(before[0].status).toBe("pending");
    expect(before[0].lastAttemptAt).toBeNull();

    const beforeMs = Date.now();
    await markInFlight(a.id);
    const afterMs = Date.now();

    const all = await getQueuedActions();
    expect(all).toHaveLength(1);
    expect(all[0].status).toBe("in-flight");
    expect(all[0].attempts).toBe(1);
    expect(typeof all[0].lastAttemptAt).toBe("number");
    expect(all[0].lastAttemptAt).not.toBeNull();
    expect(all[0].lastAttemptAt!).toBeGreaterThanOrEqual(beforeMs);
    expect(all[0].lastAttemptAt!).toBeLessThanOrEqual(afterMs);
  });

  it("markFailed transitions the action to failed and increments attempts", async () => {
    const a = await enqueueAction("markOrderReady", { orderId: "abc" });
    // Pretend it has already been tried once
    await markInFlight(a.id);

    const beforeMs = Date.now();
    await markFailed(a.id, "server returned 500");
    const afterMs = Date.now();

    const all = await getQueuedActions();
    expect(all).toHaveLength(1);
    expect(all[0].status).toBe("failed");
    expect(all[0].attempts).toBe(2); // markInFlight set it to 1, markFailed adds 1
    expect(all[0].error).toBe("server returned 500");
    expect(typeof all[0].lastAttemptAt).toBe("number");
    expect(all[0].lastAttemptAt).not.toBeNull();
    expect(all[0].lastAttemptAt!).toBeGreaterThanOrEqual(beforeMs);
    expect(all[0].lastAttemptAt!).toBeLessThanOrEqual(afterMs);
  });
});
