/**
 * Test setup for action tests with eventual consistency verification
 *
 * This setup allows actions to run (unlike the main setup which mocks them out)
 * and automatically verifies that optimistic writes match ingester behavior.
 *
 * Usage: Create test files in data/__tests__/actions/ that import this setup.
 */

import { beforeAll, beforeEach, afterAll, afterEach, vi } from "vitest";
import {
  initTestDb,
  clearTestDb,
  closeTestDb,
  resetCounters,
  resetSeqCounter,
  resetCurrentUser,
  resetTidCounter,
  getCurrentTestDid,
  resolveHandle,
  resolveDid,
  getAvatar,
} from "./helpers";
import {
  createAfterMock,
  createCapturingLexClient,
  verifyEventualConsistency,
  reset as resetEventualConsistency,
} from "./helpers/eventual-consistency";

// ============================================================================
// Global Setup
// ============================================================================

beforeAll(async () => {
  await initTestDb();
});

afterAll(async () => {
  await closeTestDb();
});

beforeEach(async () => {
  await clearTestDb();
  resetCounters();
  resetSeqCounter();
  resetTidCounter();
  resetCurrentUser();
  resetEventualConsistency();
  vi.clearAllMocks();
});

afterEach(async () => {
  // Verify eventual consistency after each test
  await verifyEventualConsistency();
});

// ============================================================================
// Module Mocks
// ============================================================================

// Mock server-only
vi.mock("server-only", () => ({}));

// Mock next/cache
vi.mock("next/cache", () => ({
  revalidatePath: vi.fn(),
  revalidateTag: vi.fn(),
  updateTag: vi.fn(),
  cacheTag: vi.fn(),
  cacheLife: vi.fn(),
  refresh: vi.fn(),
}));

// Mock next/headers
vi.mock("next/headers", () => ({
  cookies: vi.fn(async () => ({
    get: vi.fn(() => null),
    set: vi.fn(),
    delete: vi.fn(),
  })),
}));

// Mock next/navigation
class NotFoundError extends Error {
  constructor() {
    super("NEXT_NOT_FOUND");
    this.name = "NotFoundError";
  }
}

vi.mock("next/navigation", () => ({
  notFound: () => {
    throw new NotFoundError();
  },
}));

// Mock next/server - capture after() callbacks
vi.mock("next/server", () => ({
  after: createAfterMock(),
}));

// Mock @/auth
vi.mock("@/auth", () => ({
  getCurrentDid: vi.fn(() => getCurrentTestDid()),
  getCurrentUser: vi.fn(async () => {
    const did = getCurrentTestDid();
    if (!did) return null;
    const handle = resolveDid(did);
    const avatar = getAvatar(did);
    return handle ? { did, handle, avatar } : null;
  }),
  getSessionAgent: vi.fn(async () => null),
}));

// Mock @/data/lex-client - use capturing client that tracks PDS operations
const capturingClient = createCapturingLexClient(() => getCurrentTestDid() || "did:plc:test");

vi.mock("@/data/lex-client", () => ({
  getLexClient: vi.fn(async () => capturingClient),
}));

// Mock @/data/db to use test database
vi.mock("@/data/db", async () => {
  const { getTestDb, trails, walks, completions, drafts, accounts } =
    await import("./helpers/test-db");

  return {
    getDb: () => getTestDb(),
    trails,
    walks,
    completions,
    drafts,
    accounts,
    type: {} as any,
  };
});

vi.mock("@sidetrail/db", async (importOriginal) => {
  const original = await importOriginal<typeof import("@sidetrail/db")>();
  return { ...original };
});

// Mock @atproto/identity
vi.mock("@atproto/identity", () => {
  class IdResolver {
    handle = {
      resolve: async (handle: string) => resolveHandle(handle),
    };
    did = {
      resolve: async (did: string) => {
        const handle = resolveDid(did);
        if (!handle) return null;
        return {
          id: did,
          alsoKnownAs: [`at://${handle}`],
        };
      },
    };
  }

  return { IdResolver };
});

// Mock fetch
const mockFetch = vi.fn(async (url: string) => {
  if (url.includes("app.bsky.actor.getProfile")) {
    const match = url.match(/actor=([^&]+)/);
    if (match) {
      const did = decodeURIComponent(match[1]);
      const avatar = getAvatar(did);
      const handle = resolveDid(did);
      return {
        ok: true,
        json: async () => ({ did, handle: handle || did, avatar }),
      };
    }
  }
  return { ok: false, status: 404, json: async () => ({}) };
});

global.fetch = mockFetch as any;
