/**
 * Tests for account takedown/suspension handling
 *
 * Tests that:
 * - Takendown/deleted accounts have their content removed
 * - Suspended/deactivated accounts keep their content but are filtered from listings
 * - Commits from takendown/deleted accounts are ignored
 * - Reactivation restores visibility
 */

import { describe, it, expect } from "vitest";
import { emit, emitAccount, ALICE, BOB, setCurrentUser, generateTid } from "./helpers";
import { getTestDb, trails, walks, completions, accounts } from "./helpers/test-db";
import { eq, count } from "drizzle-orm";
import {
  loadTrails,
  loadTrailActiveWalkers,
  loadTrailDetail,
  loadUserPublishedTrails,
} from "../queries";

// ============================================================================
// Test Data Helpers
// ============================================================================

function createTrailRecord() {
  return {
    $type: "app.sidetrail.trail",
    title: "Test Trail",
    description: "A test trail",
    stops: [
      { tid: generateTid(), title: "Stop 1", content: "Content 1" },
      { tid: generateTid(), title: "Stop 2", content: "Content 2" },
    ],
    accentColor: "#FF0000",
    backgroundColor: "#FFFFFF",
    createdAt: new Date().toISOString(),
  };
}

function createWalkRecord(trailUri: string, trailCid: string) {
  return {
    $type: "app.sidetrail.walk",
    trail: { uri: trailUri, cid: trailCid },
    visitedStops: [],
    createdAt: new Date().toISOString(),
  };
}

function createCompletionRecord(trailUri: string, trailCid: string) {
  return {
    $type: "app.sidetrail.completion",
    trail: { uri: trailUri, cid: trailCid },
    createdAt: new Date().toISOString(),
  };
}

// ============================================================================
// Account Event Tests
// ============================================================================

describe("Account Events", () => {
  describe("account takedown", () => {
    it("deletes all content from takendown account", async () => {
      // Alice creates a trail
      const trail = await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Alice creates a walk
      await emit("app.sidetrail.walk", ALICE.did, createWalkRecord(trail.uri, trail.cid));

      // Alice creates a completion
      await emit(
        "app.sidetrail.completion",
        ALICE.did,
        createCompletionRecord(trail.uri, trail.cid),
      );

      // Verify content exists
      const db = getTestDb();
      let [trailCount] = await db.select({ count: count() }).from(trails);
      let [walkCount] = await db.select({ count: count() }).from(walks);
      let [completionCount] = await db.select({ count: count() }).from(completions);

      expect(trailCount.count).toBe(1);
      expect(walkCount.count).toBe(1);
      expect(completionCount.count).toBe(1);

      // Alice's account is taken down
      await emitAccount(ALICE.did, false, "takendown");

      // Verify all content deleted
      [trailCount] = await db.select({ count: count() }).from(trails);
      [walkCount] = await db.select({ count: count() }).from(walks);
      [completionCount] = await db.select({ count: count() }).from(completions);

      expect(trailCount.count).toBe(0);
      expect(walkCount.count).toBe(0);
      expect(completionCount.count).toBe(0);

      // Verify account status recorded
      const [accountStatus] = await db.select().from(accounts).where(eq(accounts.did, ALICE.did));

      expect(accountStatus.active).toBe(0);
      expect(accountStatus.status).toBe("takendown");
    });

    it("deletes all content from deleted account", async () => {
      // Alice creates a trail
      await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Alice's account is deleted
      await emitAccount(ALICE.did, false, "deleted");

      // Verify content deleted
      const db = getTestDb();
      const [trailCount] = await db.select({ count: count() }).from(trails);
      expect(trailCount.count).toBe(0);

      // Verify account status
      const [accountStatus] = await db.select().from(accounts).where(eq(accounts.did, ALICE.did));

      expect(accountStatus.active).toBe(0);
      expect(accountStatus.status).toBe("deleted");
    });
  });

  describe("account suspension/deactivation", () => {
    it("keeps content for suspended account", async () => {
      // Alice creates a trail
      await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Alice's account is suspended
      await emitAccount(ALICE.did, false, "suspended");

      // Verify content still exists
      const db = getTestDb();
      const [trailCount] = await db.select({ count: count() }).from(trails);
      expect(trailCount.count).toBe(1);

      // Verify account marked inactive
      const [accountStatus] = await db.select().from(accounts).where(eq(accounts.did, ALICE.did));

      expect(accountStatus.active).toBe(0);
      expect(accountStatus.status).toBe("suspended");
    });

    it("keeps content for deactivated account", async () => {
      // Alice creates a trail
      await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Alice deactivates her account
      await emitAccount(ALICE.did, false, "deactivated");

      // Verify content still exists
      const db = getTestDb();
      const [trailCount] = await db.select({ count: count() }).from(trails);
      expect(trailCount.count).toBe(1);

      // Verify account marked inactive
      const [accountStatus] = await db.select().from(accounts).where(eq(accounts.did, ALICE.did));

      expect(accountStatus.active).toBe(0);
      expect(accountStatus.status).toBe("deactivated");
    });
  });

  describe("account reactivation", () => {
    it("marks account active again on reactivation", async () => {
      // Alice creates a trail
      await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Alice deactivates her account
      await emitAccount(ALICE.did, false, "deactivated");

      // Alice reactivates her account
      await emitAccount(ALICE.did, true);

      // Verify account is active
      const db = getTestDb();
      const [accountStatus] = await db.select().from(accounts).where(eq(accounts.did, ALICE.did));

      expect(accountStatus.active).toBe(1);
      expect(accountStatus.status).toBeNull();
    });
  });

  describe("stale event handling", () => {
    it("ignores stale account events with lower seq", async () => {
      // Alice creates content first (accounts are only tracked if they have content)
      await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // First event: account suspended (seq 1)
      await emitAccount(ALICE.did, false, "suspended");

      // Second event: account active (seq 2)
      await emitAccount(ALICE.did, true);

      // Verify account is active with seq 2
      const db = getTestDb();
      const [accountStatus] = await db.select().from(accounts).where(eq(accounts.did, ALICE.did));

      expect(accountStatus.active).toBe(1);
      expect(accountStatus.seq).toBe(2);
    });
  });
});

// ============================================================================
// Query Filtering Tests
// ============================================================================

describe("Query Filtering", () => {
  describe("loadTrails", () => {
    it("filters out trails from inactive accounts", async () => {
      setCurrentUser(BOB);

      // Alice creates a trail
      await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Bob creates a trail
      await emit("app.sidetrail.trail", BOB.did, createTrailRecord());

      // Verify both trails visible
      let trailsList = await loadTrails();
      expect(trailsList.length).toBe(2);

      // Alice's account is suspended
      await emitAccount(ALICE.did, false, "suspended");

      // Verify only Bob's trail visible
      trailsList = await loadTrails();
      expect(trailsList.length).toBe(1);
      expect(trailsList[0].creator.did).toBe(BOB.did);
    });

    it("shows trails again after account reactivation", async () => {
      setCurrentUser(BOB);

      // Alice creates a trail
      await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Alice's account is deactivated
      await emitAccount(ALICE.did, false, "deactivated");

      // Verify trail hidden
      let trailsList = await loadTrails();
      expect(trailsList.length).toBe(0);

      // Alice reactivates
      await emitAccount(ALICE.did, true);

      // Verify trail visible again
      trailsList = await loadTrails();
      expect(trailsList.length).toBe(1);
    });

    it("shows trails from accounts with no status record (assume active)", async () => {
      // Alice creates a trail but has no account status record
      await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Verify trail is visible (no account record = assume active)
      const trailsList = await loadTrails();
      expect(trailsList.length).toBe(1);
    });
  });

  describe("loadTrailActiveWalkers", () => {
    it("filters out walkers from inactive accounts", async () => {
      // Alice creates a trail
      const trail = await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Bob starts walking
      await emit("app.sidetrail.walk", BOB.did, createWalkRecord(trail.uri, trail.cid));

      // Verify Bob appears as walker
      let walkers = await loadTrailActiveWalkers(trail.uri);
      expect(walkers.length).toBe(1);
      expect(walkers[0].did).toBe(BOB.did);

      // Bob's account is suspended
      await emitAccount(BOB.did, false, "suspended");

      // Verify Bob no longer appears
      walkers = await loadTrailActiveWalkers(trail.uri);
      expect(walkers.length).toBe(0);
    });

    it("shows walkers again after reactivation", async () => {
      // Alice creates a trail
      const trail = await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Bob starts walking
      await emit("app.sidetrail.walk", BOB.did, createWalkRecord(trail.uri, trail.cid));

      // Bob deactivates
      await emitAccount(BOB.did, false, "deactivated");

      // Bob reactivates
      await emitAccount(BOB.did, true);

      // Verify Bob appears again
      const walkers = await loadTrailActiveWalkers(trail.uri);
      expect(walkers.length).toBe(1);
      expect(walkers[0].did).toBe(BOB.did);
    });
  });

  describe("direct link access", () => {
    it("trail detail returns 404 for suspended account", async () => {
      // Alice creates a trail
      await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Alice's account is suspended
      await emitAccount(ALICE.did, false, "suspended");

      // Direct link to trail should 404
      await expect(loadTrailDetail("alice.test", "test_rkey")).rejects.toThrow("NEXT_NOT_FOUND");
    });

    it("trail detail returns 404 for deactivated account", async () => {
      // Alice creates a trail
      await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Alice deactivates
      await emitAccount(ALICE.did, false, "deactivated");

      // Direct link to trail should 404
      await expect(loadTrailDetail("alice.test", "test_rkey")).rejects.toThrow("NEXT_NOT_FOUND");
    });

    it("profile returns 404 for inactive account", async () => {
      // Alice creates a trail
      await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Alice's account is suspended
      await emitAccount(ALICE.did, false, "suspended");

      // Direct link to profile should 404
      await expect(loadUserPublishedTrails("alice.test")).rejects.toThrow("NEXT_NOT_FOUND");
    });

    it("trail detail works after reactivation", async () => {
      // Alice creates a trail
      const trail = await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

      // Alice deactivates then reactivates
      await emitAccount(ALICE.did, false, "deactivated");
      await emitAccount(ALICE.did, true);

      // Direct link should work again
      const detail = await loadTrailDetail("alice.test", trail.rkey);
      expect(detail.header.title).toBe("Test Trail");
    });
  });
});

// ============================================================================
// Commit Blocking Tests
// ============================================================================

describe("Commit Blocking", () => {
  it("ignores commits from takendown accounts", async () => {
    // Alice creates content first (which creates her account record)
    await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

    // Alice's account is taken down (deletes content, marks account takendown)
    await emitAccount(ALICE.did, false, "takendown");

    // Try to create another trail - should be ignored
    await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

    // Verify no trails exist (original deleted, new one blocked)
    const db = getTestDb();
    const [trailCount] = await db.select({ count: count() }).from(trails);
    expect(trailCount.count).toBe(0);
  });

  it("ignores commits from deleted accounts", async () => {
    // Alice creates content first (which creates her account record)
    await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

    // Alice's account is deleted
    await emitAccount(ALICE.did, false, "deleted");

    // Try to create another trail - should be ignored
    await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

    // Verify no trails exist
    const db = getTestDb();
    const [trailCount] = await db.select({ count: count() }).from(trails);
    expect(trailCount.count).toBe(0);
  });

  it("ignores commits from suspended accounts", async () => {
    // Alice creates content first (which creates her account record)
    await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

    // Alice's account is suspended
    await emitAccount(ALICE.did, false, "suspended");

    // Try to create another trail - should be ignored
    await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

    // Verify only original trail exists (content kept but new commits blocked)
    const db = getTestDb();
    const [trailCount] = await db.select({ count: count() }).from(trails);
    expect(trailCount.count).toBe(1);
  });

  it("ignores commits from deactivated accounts", async () => {
    // Alice creates content first (which creates her account record)
    await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

    // Alice's account is deactivated
    await emitAccount(ALICE.did, false, "deactivated");

    // Try to create another trail - should be ignored
    await emit("app.sidetrail.trail", ALICE.did, createTrailRecord());

    // Verify only original trail exists (content kept but new commits blocked)
    const db = getTestDb();
    const [trailCount] = await db.select({ count: count() }).from(trails);
    expect(trailCount.count).toBe(1);
  });
});
