/**
 * Global test setup — mock heavy external dependencies so unit tests
 * never touch real databases, APIs, or file-system keys.
 */

import { mock } from "bun:test";
import crypto from "crypto";

// ---------------------------------------------------------------------------
// Generate a real RSA key pair for modules that call crypto.createPrivateKey()
// at import time (e.g. readSecureValue.ts).
// ---------------------------------------------------------------------------
const { privateKey: _testPrivateKey, publicKey: _testPublicKey } =
  crypto.generateKeyPairSync("rsa", {
    modulusLength: 2048,
    privateKeyEncoding: { type: "pkcs8", format: "pem" },
    publicKeyEncoding: { type: "spki", format: "pem" },
  });

// ---------------------------------------------------------------------------
// Mock the globalEvents module — many source files import `events` from here.
// We provide both `events` and `setupEventDebugger` so that no test
// encounters "export not found" when another test's mock.module is stale.
// ---------------------------------------------------------------------------

mock.module("../src/modules/globalEvents", () => ({
  events: {
    emit: mock(),
    on: mock(),
    off: mock(),
    once: mock(),
    removeAllListeners: mock(),
  },
  setupEventDebugger: mock(),
}));

// ---------------------------------------------------------------------------
// Mock modules that are commonly mocked by test files at top-level.
// Having them in the preload ensures that even when per-test mock.module
// calls persist globally, the baseline mock is complete.
// ---------------------------------------------------------------------------

mock.module("../src/modules/fetchMicrosoftUser", () => ({
  fetchMicrosoftUser: mock(() => Promise.resolve({})),
}));

// ---------------------------------------------------------------------------
// Mock workert — instantiates PgBoss at module load time which requires
// DATABASE_URL. Tests don't have a real DB, so we stub all exports.
// ---------------------------------------------------------------------------
mock.module("../src/workert", () => ({
  getBoss: mock(() => ({
    send: mock(() => Promise.resolve("mock-job-id")),
    createQueue: mock(() => Promise.resolve()),
    start: mock(() => Promise.resolve()),
    on: mock(),
  })),
  initializeWorkerSystem: mock(() => Promise.resolve()),
  ensureManagerSocketReady: mock(() => Promise.resolve({ on: mock(), emit: mock() })),
  reserveWorkerId: mock(() => Promise.resolve("mock-worker-id")),
}));

mock.module("../src/managers/sessions", () => ({
  sessions: {
    create: mock(() =>
      Promise.resolve({
        accessToken: "mock-access",
        refreshToken: "mock-refresh",
      })
    ),
    fetch: mock(() => Promise.resolve(null)),
  },
}));

// ---------------------------------------------------------------------------
// Mock the constants module — almost every source file imports from here.
// We provide safe defaults so modules can be imported without side-effects.
// ---------------------------------------------------------------------------

mock.module("../src/constants", () => ({
  prisma: createMockPrisma(),
  PORT: "3333",
  API_BASE_URL: "http://localhost:3333",
  sessionDuration: 30 * 24 * 60 * 60_000,
  accessTokenDuration: "10min",
  refreshTokenDuration: "30d",
  accessTokenPrivateKey: _testPrivateKey,
  refreshTokenPrivateKey: _testPrivateKey,
  permissionsPrivateKey: _testPrivateKey,
  secureValuesPrivateKey: _testPrivateKey,
  secureValuesPublicKey: _testPublicKey,
  msalClient: { acquireTokenByCode: mock(() => Promise.resolve({})) },
  connectWiseApi: {
    get: mock(() => Promise.resolve({ data: {} })),
    post: mock(() => Promise.resolve({ data: {} })),
    put: mock(() => Promise.resolve({ data: {} })),
    patch: mock(() => Promise.resolve({ data: {} })),
    delete: mock(() => Promise.resolve({ data: {} })),
  },
  redis: {
    get: mock(() => Promise.resolve(null)),
    set: mock(() => Promise.resolve("OK")),
    del: mock(() => Promise.resolve(1)),
  },
  unifi: createMockUnifi(),
  unifiControllerBaseUrl: "https://unifi.test.local",
  unifiSite: "default",
  unifiUsername: "admin",
  unifiPassword: "test-pass",
  io: { of: mock(() => ({ on: mock() })) },
  engine: {},
  collectorSocket: {
    connected: false,
    connect: mock(),
    disconnect: mock(),
    on: mock(),
    emit: mock(),
  },
  connectCollectorSocket: mock(),
}));

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/**
 * Build a complete mock constants object for use with `mock.module()`.
 *
 * Pass `overrides` to replace specific exports (e.g. a custom prisma mock).
 * All keys from the preload mock are included so that downstream modules
 * importing named exports (secureValuesPublicKey, connectWiseApi, etc.)
 * never encounter "export not found" errors.
 */
export function buildMockConstants(
  overrides: Record<string, any> = {}
): Record<string, any> {
  return {
    prisma: createMockPrisma(),
    PORT: "3333",
    API_BASE_URL: "http://localhost:3333",
    sessionDuration: 30 * 24 * 60 * 60_000,
    accessTokenDuration: "10min",
    refreshTokenDuration: "30d",
    accessTokenPrivateKey: _testPrivateKey,
    refreshTokenPrivateKey: _testPrivateKey,
    permissionsPrivateKey: _testPrivateKey,
    secureValuesPrivateKey: _testPrivateKey,
    secureValuesPublicKey: _testPublicKey,
    msalClient: { acquireTokenByCode: mock(() => Promise.resolve({})) },
    connectWiseApi: {
      get: mock(() => Promise.resolve({ data: {} })),
      post: mock(() => Promise.resolve({ data: {} })),
      put: mock(() => Promise.resolve({ data: {} })),
      patch: mock(() => Promise.resolve({ data: {} })),
      delete: mock(() => Promise.resolve({ data: {} })),
    },
    redis: {
      get: mock(() => Promise.resolve(null)),
      set: mock(() => Promise.resolve("OK")),
      del: mock(() => Promise.resolve(1)),
    },
    unifi: createMockUnifi(),
    unifiControllerBaseUrl: "https://unifi.test.local",
    unifiSite: "default",
    unifiUsername: "admin",
    unifiPassword: "test-pass",
    io: { of: mock(() => ({ on: mock() })) },
    engine: {},
    collectorSocket: {
      connected: false,
      connect: mock(),
      disconnect: mock(),
      on: mock(),
      emit: mock(),
    },
    connectCollectorSocket: mock(),
    ...overrides,
  };
}

/**
 * Build a complete mock globalEvents object for use with `mock.module()`.
 * Includes both `events` and `setupEventDebugger` so downstream modules
 * never encounter "export not found" errors.
 */
export function buildMockGlobalEvents(
  overrides: Record<string, any> = {}
): Record<string, any> {
  return {
    events: {
      emit: mock(),
      on: mock(),
      off: mock(),
      once: mock(),
      removeAllListeners: mock(),
    },
    setupEventDebugger: mock(),
    ...overrides,
  };
}

export function createMockPrisma() {
  const createModelProxy = () =>
    new Proxy(
      {},
      {
        get(_target, prop) {
          return mock(() => Promise.resolve(null));
        },
      }
    );

  return new Proxy(
    {},
    {
      get(_target, prop) {
        if (prop === "$connect" || prop === "$disconnect")
          return mock(() => Promise.resolve());
        return createModelProxy();
      },
    }
  );
}

export function createMockUnifi() {
  return {
    login: mock(() => Promise.resolve()),
    getAllSites: mock(() => Promise.resolve([])),
    getSiteOverview: mock(() => Promise.resolve({})),
    getDevices: mock(() => Promise.resolve([])),
    getWlanConf: mock(() => Promise.resolve([])),
    updateWlanConf: mock(() => Promise.resolve({})),
    getNetworks: mock(() => Promise.resolve([])),
    createSite: mock(() =>
      Promise.resolve({ name: "default", description: "Default" })
    ),
    getWlanGroups: mock(() => Promise.resolve([])),
    createWlanGroup: mock(() => Promise.resolve({})),
    getUserGroups: mock(() => Promise.resolve([])),
    createUserGroup: mock(() => Promise.resolve({})),
    getApGroups: mock(() => Promise.resolve([])),
    createApGroup: mock(() => Promise.resolve({})),
    updateApGroup: mock(() => Promise.resolve({})),
    getAccessPoints: mock(() => Promise.resolve([])),
    getWifiLimits: mock(() => Promise.resolve({})),
    getPrivatePSKs: mock(() => Promise.resolve([])),
    createPrivatePSK: mock(() => Promise.resolve({})),
  };
}

/** Build a minimal Prisma-shaped User row for controller tests. */
export function buildMockUser(overrides: Record<string, any> = {}) {
  return {
    id: "user-1",
    userId: "ms-uid-1",
    firstName: "Test",
    lastName: "User",
    login: "test@example.com",
    email: "test@example.com",
    emailVerified: null,
    image: null,
    token: "ms-token",
    permissions: null,
    createdAt: new Date("2025-01-01"),
    updatedAt: new Date("2025-01-01"),
    roles: [],
    ...overrides,
  };
}

/** Build a minimal Prisma-shaped Role row. */
export function buildMockRole(overrides: Record<string, any> = {}) {
  return {
    id: "role-1",
    title: "Test Role",
    moniker: "test-role",
    permissions: "mock-permissions-token",
    createdAt: new Date("2025-01-01"),
    updatedAt: new Date("2025-01-01"),
    users: [],
    ...overrides,
  };
}

/** Build a minimal Prisma-shaped Company row. */
export function buildMockCompany(overrides: Record<string, any> = {}) {
  return {
    id: 123,
    uid: "company-1",
    identifier: "TestCo",
    name: "Test Company",
    phone: "555-1234",
    website: "https://test.com",
    contacts: [],
    companyAddresses: [],
    createdAt: new Date("2025-01-01"),
    updatedAt: new Date("2025-01-01"),
    ...overrides,
  };
}

/** Build a minimal Session row. */
export function buildMockSession(overrides: Record<string, any> = {}) {
  return {
    id: "session-1",
    sessionKey: "sk-abc123",
    userId: "user-1",
    expires: new Date(Date.now() + 30 * 24 * 60 * 60_000),
    refreshedAt: null,
    invalidatedAt: null,
    refreshTokenGenerated: false,
    ...overrides,
  };
}

/** Build a minimal CredentialType row. */
export function buildMockCredentialType(overrides: Record<string, any> = {}) {
  return {
    id: "ctype-1",
    name: "Login Credential",
    permissionScope: "credential.login",
    icon: null,
    fields: [
      {
        id: "username",
        name: "Username",
        required: true,
        secure: false,
        valueType: "plain_text",
      },
      {
        id: "password",
        name: "Password",
        required: true,
        secure: true,
        valueType: "password",
      },
    ],
    credentials: [],
    createdAt: new Date("2025-01-01"),
    updatedAt: new Date("2025-01-01"),
    ...overrides,
  };
}

/** Build a minimal Credential row. */
export function buildMockCredential(overrides: Record<string, any> = {}) {
  const ctype = buildMockCredentialType();
  const company = buildMockCompany();
  return {
    id: "cred-1",
    name: "Test Credential",
    notes: null,
    typeId: ctype.id,
    companyId: company.uid,
    subCredentialOfId: null,
    fields: { username: "admin" },
    type: ctype,
    company,
    securevalues: [
      {
        id: "sv-1",
        name: "password",
        content: "encrypted-data",
        hash: "BLAKE2s$abc$salt",
        credentialId: "cred-1",
        createdAt: new Date("2025-01-01"),
        updatedAt: new Date("2025-01-01"),
      },
    ],
    subCredentials: [],
    createdAt: new Date("2025-01-01"),
    updatedAt: new Date("2025-01-01"),
    ...overrides,
  };
}

/** Build a minimal UnifiSite row. */
export function buildMockUnifiSite(overrides: Record<string, any> = {}) {
  return {
    id: "usite-1",
    name: "Main Office",
    siteId: "default",
    companyId: null,
    createdAt: new Date("2025-01-01"),
    updatedAt: new Date("2025-01-01"),
    ...overrides,
  };
}

/** Build a minimal Prisma-shaped Opportunity row. */
export function buildMockOpportunity(overrides: Record<string, any> = {}) {
  return {
    uid: "opp-1",
    id: 1001,
    name: "Test Opportunity",
    notes: "Some notes",
    typeId: 1,
    type: { id: 1, name: "New Business" },
    stage: { id: 2, name: "Proposal" },
    stageName: "Proposal",
    stageCwId: 2,
    statusId: 3,
    status: { id: 3, name: "Active" },
    priorityName: "High",
    priorityCwId: 4,
    ratingName: "Hot",
    ratingCwId: 5,
    source: "Referral",
    campaignName: null,
    campaignCwId: null,
    primarySalesRepName: "John",
    primarySalesRepIdentifier: "jroberts",
    primarySalesRepCwId: 10,
    secondarySalesRepName: null,
    secondarySalesRepIdentifier: null,
    secondarySalesRepCwId: null,
    companyCwId: 123,
    companyName: "Test Company",
    companyId: 123,
    contactCwId: 200,
    contactName: "Jane Doe",
    siteCwId: 300,
    siteName: "Main Office",
    customerPO: "PO-12345",
    totalSalesTax: 50.0,
    probability: 80,
    locationName: "HQ",
    locationCwId: 400,
    departmentName: "Sales",
    departmentCwId: 500,
    expectedCloseDate: new Date("2026-04-01"),
    pipelineChangeDate: new Date("2026-02-15"),
    dateBecameLead: new Date("2026-01-01"),
    closedDate: null,
    closedFlag: false,
    closedByName: null,
    closedByCwId: null,
    cwLastUpdated: new Date("2026-02-28"),
    cwDateEntered: new Date("2026-01-01"),
    productSequence: [],
    createdAt: new Date("2026-01-01"),
    updatedAt: new Date("2026-02-28"),
    company: null,
    ...overrides,
  };
}

/** Build a minimal CW Activity object for ActivityController tests. */
export function buildMockCWActivity(overrides: Record<string, any> = {}) {
  return {
    id: 5001,
    name: "Test Activity",
    notes: "Activity notes",
    type: { id: 1, name: "Call" },
    status: { id: 2, name: "Open" },
    company: { id: 123, identifier: "TestCo", name: "Test Company" },
    contact: { id: 200, name: "Jane Doe" },
    phoneNumber: "555-1234",
    email: "jane@test.com",
    opportunity: { id: 1001, name: "Test Opportunity" },
    ticket: { id: 0, name: "" },
    agreement: { id: 0, name: "" },
    campaign: { id: 0, name: "" },
    assignTo: { id: 10, identifier: "jroberts", name: "John Roberts" },
    scheduleStatus: { id: 1, name: "Firm" },
    reminder: { id: 1, name: "15 Minutes" },
    where: { id: 1, name: "Office" },
    dateStart: "2026-03-01T09:00:00Z",
    dateEnd: "2026-03-01T10:00:00Z",
    notifyFlag: false,
    mobileGuid: "guid-abc123",
    currency: { id: 1, name: "USD" },
    customFields: [],
    _info: {
      lastUpdated: "2026-02-28T12:00:00Z",
      updatedBy: "jroberts",
      dateEntered: "2026-01-15T08:00:00Z",
      enteredBy: "jroberts",
    },
    ...overrides,
  };
}

/** Build a minimal CW Forecast Item for ForecastProductController tests. */
export function buildMockCWForecastItem(overrides: Record<string, any> = {}) {
  return {
    id: 7001,
    forecastDescription: "Network Switch",
    opportunity: { id: 1001, name: "Test Opportunity" },
    quantity: 5,
    status: { id: 1, name: "Won" },
    catalogItem: { id: 500, identifier: "USW-Pro-24" },
    productDescription: "UniFi Switch Pro 24",
    productClass: "Product",
    forecastType: "Product",
    revenue: 2500.0,
    cost: 1800.0,
    margin: 700.0,
    percentage: 100,
    includeFlag: true,
    linkFlag: false,
    recurringFlag: false,
    taxableFlag: true,
    recurringRevenue: 0,
    recurringCost: 0,
    cycles: 0,
    sequenceNumber: 1,
    subNumber: 0,
    quoteWerksQuantity: 0,
    _info: {
      lastUpdated: "2026-02-28T12:00:00Z",
      updatedBy: "jroberts",
    },
    ...overrides,
  };
}

/** Build a minimal Prisma-shaped GeneratedQuotes row. */
export function buildMockGeneratedQuote(overrides: Record<string, any> = {}) {
  return {
    id: "quote-1",
    quoteRegenData: { theme: "default" },
    quoteFile: new Uint8Array([0x25, 0x50, 0x44, 0x46]),
    quoteFileName: "Quote-TestOpp.pdf",
    opportunityId: "opp-1",
    createdById: "user-1",
    createdAt: new Date("2026-03-01"),
    updatedAt: new Date("2026-03-01"),
    opportunity: null,
    createdBy: null,
    ...overrides,
  };
}

/** Build a minimal Prisma-shaped CatalogItem row. */
export function buildMockCatalogItem(overrides: Record<string, any> = {}) {
  return {
    id: 500,
    uid: "cat-1",
    identifier: "USW-Pro-24",
    name: "UniFi Switch Pro 24",
    description: "24-port managed switch",
    customerDescription: "Enterprise switch",
    internalNotes: null,
    subcategory: {
      id: 112,
      name: "Network-Switch",
      category: { id: 18, name: "Technology" },
    },
    manufacturer: { id: 248, name: "Ubiquiti" },
    partNumber: "USW-Pro-24",
    vendorName: "Ubiquiti Inc",
    vendorSku: "USW-Pro-24",
    vendorCwId: 100,
    price: 500.0,
    cost: 360.0,
    inactive: false,
    salesTaxable: true,
    onHand: 10,
    cwLastUpdated: new Date("2026-02-28"),
    linkedItems: [],
    createdAt: new Date("2026-01-01"),
    updatedAt: new Date("2026-02-28"),
    ...overrides,
  };
}
