/**
 * Test setup for integration tests
 *
 * This setup uses a real PostgreSQL database and simulates Jetstream events
 * by directly inserting into the database. The queries run against real data.
 */

import { beforeAll, beforeEach, afterAll, vi } from "vitest";
import {
  initTestDb,
  clearTestDb,
  closeTestDb,
  resetCounters,
  resetSeqCounter,
  resetCurrentUser,
  resetTidCounter,
  getCurrentTestDid,
  resolveHandle,
  resolveDid,
  getAvatar,
} from "./helpers";

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

beforeAll(async () => {
  // Initialize test database with fresh schema
  await initTestDb();
});

afterAll(async () => {
  // Close database connection
  await closeTestDb();
});

beforeEach(async () => {
  // Clear all data between tests
  await clearTestDb();

  // Reset counters
  resetCounters();
  resetSeqCounter();
  resetTidCounter();
  resetCurrentUser();

  // Clear mocks
  vi.clearAllMocks();
});

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

// Mock server-only (allow server code to run in tests)
vi.mock("server-only", () => ({}));

// Mock next/cache (cache operations are no-ops in tests)
vi.mock("next/cache", () => ({
  revalidatePath: vi.fn(),
  revalidateTag: vi.fn(),
  updateTag: vi.fn(),
  cacheTag: vi.fn(),
  cacheLife: vi.fn(),
}));

// Mock next/headers (no cookies in tests)
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 @/auth to use test users
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 (PDS operations are not used in integration tests)
// Actions that would write to PDS are tested via event emission instead
vi.mock("@/data/lex-client", () => ({
  getLexClient: vi.fn(async () => {
    throw new Error(
      "getLexClient should not be called in integration tests. " +
        "Use event emission (emit.trail.create, etc.) to simulate PDS writes.",
    );
  }),
}));

// Mock @/data/db to use the 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,
    // Re-export types from schema
    type: {} as any,
  };
});

// Re-export types for the db mock
vi.mock("@sidetrail/db", async (importOriginal) => {
  const original = await importOriginal<typeof import("@sidetrail/db")>();
  return {
    ...original,
    // These are already in original, just re-exporting
  };
});

// Mock @atproto/identity to use test users
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 for avatar lookups
const mockFetch = vi.fn(async (url: string) => {
  // Handle bsky avatar requests
  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,
        }),
      };
    }
  }

  // Default: return empty response
  return {
    ok: false,
    status: 404,
    json: async () => ({}),
  };
});

global.fetch = mockFetch as any;
