/**
 * Drafts Tests
 *
 * Scenarios covering draft storage with access control,
 * CRUD operations, and conflict detection.
 */

import { describe, it, expect, beforeEach, vi } from "vitest";
import { ALICE, BOB, setCurrentUser, generateTid } from "./helpers";

// Import after mocks are set up (via setup.ts)
import * as queries from "../drafts/queries";
import * as actions from "../drafts/actions";

const makeDraft = (overrides: Partial<Parameters<typeof actions.saveDraft>[1]> = {}) => ({
  title: "Test Draft",
  description: "A test draft",
  stops: [
    { tid: generateTid(), title: "Stop 1", content: "Content 1" },
    { tid: generateTid(), title: "Stop 2", content: "Content 2" },
  ],
  accentColor: "#ff5500",
  backgroundColor: "#fff8f0",
  ...overrides,
});

describe("Server Drafts - Access Control", () => {
  it("requires authentication to list drafts", async () => {
    setCurrentUser(null);
    await expect(queries.loadDrafts()).rejects.toThrow("Authentication required");
  });

  it("requires authentication to get draft detail", async () => {
    setCurrentUser(null);
    await expect(queries.loadDraftDetail("some-rkey")).rejects.toThrow("Authentication required");
  });

  it("requires authentication to save draft", async () => {
    setCurrentUser(null);
    await expect(actions.saveDraft("some-rkey", makeDraft())).rejects.toThrow(
      "Authentication required",
    );
  });

  it("requires authentication to delete draft", async () => {
    setCurrentUser(null);
    await expect(actions.deleteDraft("some-rkey")).rejects.toThrow("Authentication required");
  });

  it("user can only see their own drafts", async () => {
    // Alice creates a draft
    setCurrentUser(ALICE);
    await actions.saveDraft("alice-draft", makeDraft({ title: "Alice's Draft" }));

    // Bob creates a draft
    setCurrentUser(BOB);
    await actions.saveDraft("bob-draft", makeDraft({ title: "Bob's Draft" }));

    // Alice only sees her draft
    setCurrentUser(ALICE);
    const aliceDrafts = await queries.loadDrafts();
    expect(aliceDrafts).toHaveLength(1);
    expect(aliceDrafts[0].title).toBe("Alice's Draft");

    // Bob only sees his draft
    setCurrentUser(BOB);
    const bobDrafts = await queries.loadDrafts();
    expect(bobDrafts).toHaveLength(1);
    expect(bobDrafts[0].title).toBe("Bob's Draft");
  });

  it("user cannot access another user's draft by rkey", async () => {
    setCurrentUser(ALICE);
    await actions.saveDraft("secret", makeDraft({ title: "Alice's Secret" }));

    setCurrentUser(BOB);
    const draft = await queries.loadDraftDetail("secret");
    expect(draft).toBeNull(); // Bob can't see Alice's draft
  });

  it("user cannot delete another user's draft", async () => {
    setCurrentUser(ALICE);
    await actions.saveDraft("protected", makeDraft({ title: "Protected" }));

    setCurrentUser(BOB);
    await actions.deleteDraft("protected"); // Silently does nothing

    setCurrentUser(ALICE);
    const draft = await queries.loadDraftDetail("protected");
    expect(draft).not.toBeNull(); // Still exists
  });
});

describe("Server Drafts - Creating", () => {
  beforeEach(() => {
    setCurrentUser(ALICE);
  });

  it("creates a new draft with createDraft action", async () => {
    const rkey = await actions.createDraft();

    expect(rkey).toBeTruthy();

    const draft = await queries.loadDraftDetail(rkey);
    expect(draft).not.toBeNull();
    expect(draft!.title).toBe(""); // Empty title for new draft
    expect(draft!.stops).toHaveLength(2); // Default 2 stops
  });

  it("creates a draft with saveDraft", async () => {
    const result = await actions.saveDraft("new-draft", makeDraft({ title: "My Draft" }));

    expect(result.success).toBe(true);
    expect(result.version).toBe(1);

    const draft = await queries.loadDraftDetail("new-draft");
    expect(draft?.title).toBe("My Draft");
    expect(draft?.version).toBe(1);
  });

  it("new draft gets timestamps set", async () => {
    const before = Date.now();
    await actions.saveDraft("timestamped", makeDraft());
    const after = Date.now();

    const draft = await queries.loadDraftDetail("timestamped");

    expect(draft!.createdAt.getTime()).toBeGreaterThanOrEqual(before);
    expect(draft!.createdAt.getTime()).toBeLessThanOrEqual(after);
    expect(draft!.updatedAt.getTime()).toBeGreaterThanOrEqual(before);
    expect(draft!.updatedAt.getTime()).toBeLessThanOrEqual(after);
  });
});

describe("Server Drafts - Updating", () => {
  beforeEach(() => {
    setCurrentUser(ALICE);
  });

  it("updates existing draft and increments version", async () => {
    await actions.saveDraft("updating", makeDraft({ title: "V1" }));

    const result = await actions.saveDraft("updating", makeDraft({ title: "V2" }));

    expect(result.version).toBe(2);

    const draft = await queries.loadDraftDetail("updating");
    expect(draft?.title).toBe("V2");
    expect(draft?.version).toBe(2);
  });

  it("updating updates the updatedAt timestamp", async () => {
    vi.useFakeTimers();

    await actions.saveDraft("timing", makeDraft({ title: "Original" }));
    const before = await queries.loadDraftDetail("timing");

    vi.advanceTimersByTime(5000);

    await actions.saveDraft("timing", makeDraft({ title: "Updated" }));
    const after = await queries.loadDraftDetail("timing");

    expect(after!.updatedAt.getTime()).toBeGreaterThan(before!.updatedAt.getTime());
    // createdAt should stay the same (or close - depends on implementation)

    vi.useRealTimers();
  });

  it("preserves all fields when updating", async () => {
    const original = makeDraft({
      title: "Original Title",
      description: "Original Description",
      accentColor: "#111111",
      backgroundColor: "#eeeeee",
    });
    await actions.saveDraft("preserve", original);

    const updated = makeDraft({
      title: "Updated Title",
      description: "Updated Description",
      accentColor: "#222222",
      backgroundColor: "#dddddd",
    });
    await actions.saveDraft("preserve", updated);

    const draft = await queries.loadDraftDetail("preserve");
    expect(draft?.title).toBe("Updated Title");
    expect(draft?.description).toBe("Updated Description");
    expect(draft?.accentColor).toBe("#222222");
    expect(draft?.backgroundColor).toBe("#dddddd");
  });
});

describe("Server Drafts - Deleting", () => {
  beforeEach(() => {
    setCurrentUser(ALICE);
  });

  it("deleted draft disappears from list", async () => {
    await actions.saveDraft("to-delete", makeDraft({ title: "To Delete" }));

    const beforeList = await queries.loadDrafts();
    expect(beforeList).toHaveLength(1);

    await actions.deleteDraft("to-delete");

    const afterList = await queries.loadDrafts();
    expect(afterList).toHaveLength(0);
  });

  it("deleted draft detail returns null", async () => {
    await actions.saveDraft("temporary", makeDraft({ title: "Temporary" }));

    await actions.deleteDraft("temporary");

    const draft = await queries.loadDraftDetail("temporary");
    expect(draft).toBeNull();
  });

  it("deleting one draft does not affect others", async () => {
    await actions.saveDraft("draft1", makeDraft({ title: "Draft 1" }));
    await actions.saveDraft("draft2", makeDraft({ title: "Draft 2" }));

    await actions.deleteDraft("draft1");

    const remaining = await queries.loadDrafts();
    expect(remaining).toHaveLength(1);
    expect(remaining[0].rkey).toBe("draft2");
    expect(remaining[0].title).toBe("Draft 2");
  });

  it("deleting non-existent draft is a no-op", async () => {
    // Should not throw
    await actions.deleteDraft("nonexistent");
  });
});

describe("Server Drafts - Listing and Ordering", () => {
  beforeEach(() => {
    setCurrentUser(ALICE);
  });

  it("drafts are ordered by updatedAt descending (newest first)", async () => {
    vi.useFakeTimers();

    await actions.saveDraft("old", makeDraft({ title: "Old Draft" }));

    vi.advanceTimersByTime(1000);

    await actions.saveDraft("new", makeDraft({ title: "New Draft" }));

    const drafts = await queries.loadDrafts();

    expect(drafts).toHaveLength(2);
    expect(drafts[0].title).toBe("New Draft");
    expect(drafts[1].title).toBe("Old Draft");

    vi.useRealTimers();
  });

  it("editing a draft moves it to top of list", async () => {
    vi.useFakeTimers();

    await actions.saveDraft("first", makeDraft({ title: "First Draft" }));

    vi.advanceTimersByTime(1000);

    await actions.saveDraft("second", makeDraft({ title: "Second Draft" }));

    // Second draft is on top
    let drafts = await queries.loadDrafts();
    expect(drafts[0].title).toBe("Second Draft");

    vi.advanceTimersByTime(1000);

    // Edit the first draft
    await actions.saveDraft("first", makeDraft({ title: "First Draft (Edited)" }));

    // Now first draft is on top
    drafts = await queries.loadDrafts();
    expect(drafts[0].title).toBe("First Draft (Edited)");

    vi.useRealTimers();
  });

  it("list shows correct stops count", async () => {
    await actions.saveDraft(
      "three-stops",
      makeDraft({
        title: "Three Stops",
        stops: [
          { tid: generateTid(), title: "S1", content: "C1" },
          { tid: generateTid(), title: "S2", content: "C2" },
          { tid: generateTid(), title: "S3", content: "C3" },
        ],
      }),
    );

    const drafts = await queries.loadDrafts();
    expect(drafts[0].stopsCount).toBe(3);
  });
});

describe("Server Drafts - Conflict Detection", () => {
  beforeEach(() => {
    setCurrentUser(ALICE);
  });

  it("detects when saving over a newer version", async () => {
    // Initial save
    const r1 = await actions.saveDraft("conflict", makeDraft({ title: "V1" }));
    expect(r1.version).toBe(1);

    // Simulate another tab/device saving (version becomes 2)
    await actions.saveDraft("conflict", makeDraft({ title: "V2 from other tab" }));

    // Original tab tries to save with stale version
    const r3 = await actions.saveDraft(
      "conflict",
      makeDraft({ title: "V3 from original tab" }),
      1, // Expected version 1, but server has 2
    );

    expect(r3.success).toBe(true);
    expect(r3.warning).toBe("overwrote_newer");
    expect(r3.version).toBe(3);
  });

  it("no warning when saving with current version", async () => {
    const r1 = await actions.saveDraft("no-conflict", makeDraft({ title: "V1" }));

    const r2 = await actions.saveDraft(
      "no-conflict",
      makeDraft({ title: "V2" }),
      r1.version, // Expected version matches
    );

    expect(r2.success).toBe(true);
    expect(r2.warning).toBeUndefined();
  });

  it("no warning when expectedVersion not provided", async () => {
    await actions.saveDraft("no-check", makeDraft({ title: "V1" }));
    await actions.saveDraft("no-check", makeDraft({ title: "V2" }));

    // Third save without version check
    const r3 = await actions.saveDraft("no-check", makeDraft({ title: "V3" }));

    expect(r3.success).toBe(true);
    expect(r3.warning).toBeUndefined();
  });
});

describe("Server Drafts - Draft Badges", () => {
  beforeEach(() => {
    setCurrentUser(ALICE);
  });

  it("loadDraftsBadges returns badges for drafts", async () => {
    await actions.saveDraft("badge1", makeDraft({ accentColor: "#ff0000" }));
    await actions.saveDraft("badge2", makeDraft({ accentColor: "#00ff00" }));

    const badges = await queries.loadDraftsBadges();
    expect(badges).toHaveLength(2);
    expect(badges.map((b) => b.accentColor)).toContain("#ff0000");
    expect(badges.map((b) => b.accentColor)).toContain("#00ff00");
  });

  it("badges show max 3 drafts", async () => {
    for (let i = 0; i < 5; i++) {
      await actions.saveDraft(`badge${i}`, makeDraft({ accentColor: `#${i}${i}${i}${i}${i}${i}` }));
    }

    const badges = await queries.loadDraftsBadges();
    expect(badges).toHaveLength(3);
  });

  it("badges require authentication", async () => {
    setCurrentUser(null);
    await expect(queries.loadDraftsBadges()).rejects.toThrow("Authentication required");
  });
});

describe("Server Drafts - Empty States", () => {
  beforeEach(() => {
    setCurrentUser(ALICE);
  });

  it("loadDrafts returns empty array when no drafts", async () => {
    const drafts = await queries.loadDrafts();
    expect(drafts).toEqual([]);
  });

  it("loadDraftDetail returns null for non-existent rkey", async () => {
    const detail = await queries.loadDraftDetail("nonexistent");
    expect(detail).toBeNull();
  });
});
