import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { RecordingRealtimeBroadcaster } from "@repo/core-testing/instrumentation";
import { RealtimeHandlerRegistry } from "@repo/core-realtime";

vi.mock("@repo/core-cms", () => ({ default: Promise.resolve({}) }));
vi.mock("payload", () => ({ getPayload: vi.fn(async () => ({ jobs: { queue: vi.fn() } })) }));
vi.mock("@repo/blog/di/bind-production", () => ({ bindProductionBlog: vi.fn() }));
vi.mock("@repo/auth/di/bind-production", () => ({ bindProductionAuth: vi.fn() }));
vi.mock("@repo/marketing-pages/di/bind-production", () => ({ bindProductionMarketingPages: vi.fn() }));
vi.mock("@repo/navigation/di/bind-production", () => ({ bindProductionNavigation: vi.fn() }));
vi.mock("@repo/media/di/bind-production", () => ({ bindProductionMedia: vi.fn() }));
vi.mock("@repo/blog/di/bind-dev-seed", () => ({ bindDevSeedBlog: vi.fn() }));
vi.mock("@repo/auth/di/bind-dev-seed", () => ({ bindDevSeedAuth: vi.fn() }));
vi.mock("@repo/marketing-pages/di/bind-dev-seed", () => ({ bindDevSeedMarketingPages: vi.fn() }));
vi.mock("@repo/navigation/di/bind-dev-seed", () => ({ bindDevSeedNavigation: vi.fn() }));
vi.mock("@repo/media/di/bind-dev-seed", () => ({ bindDevSeedMedia: vi.fn() }));
vi.mock("@repo/core-shared/instrumentation", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@repo/core-shared/instrumentation")>();
  return {
    ...actual,
    bindSentryInstrumentation: vi.fn(actual.bindSentryInstrumentation),
    bindNoopInstrumentation: vi.fn(actual.bindNoopInstrumentation),
  };
});

function makeDeps() {
  return {
    realtime: new RecordingRealtimeBroadcaster(),
    realtimeRegistry: new RealtimeHandlerRegistry(),
  };
}

describe("bindAllProduction", () => {
  beforeEach(() => {
    vi.resetModules();
    vi.clearAllMocks();
  });

  it("binds all five feature production repos", async () => {
    const { bindAllProduction } = await import("./bind-production");
    const { bindProductionBlog } = await import("@repo/blog/di/bind-production");
    const { bindProductionAuth } = await import("@repo/auth/di/bind-production");
    const { bindProductionMarketingPages } = await import("@repo/marketing-pages/di/bind-production");
    const { bindProductionNavigation } = await import("@repo/navigation/di/bind-production");
    const { bindProductionMedia } = await import("@repo/media/di/bind-production");

    await bindAllProduction(makeDeps());

    expect(bindProductionBlog).toHaveBeenCalledOnce();
    expect(bindProductionAuth).toHaveBeenCalledOnce();
    expect(bindProductionMarketingPages).toHaveBeenCalledOnce();
    expect(bindProductionNavigation).toHaveBeenCalledOnce();
    expect(bindProductionMedia).toHaveBeenCalledOnce();
  });

  it("is idempotent — second call does not re-bind", async () => {
    const { bindAllProduction } = await import("./bind-production");
    const { bindProductionBlog } = await import("@repo/blog/di/bind-production");
    const deps = makeDeps();
    await bindAllProduction(deps);
    await bindAllProduction(deps);
    expect(bindProductionBlog).toHaveBeenCalledOnce();
  });

  it("passes a Payload-backed bus + queue to each per-feature binder", async () => {
    const { bindAllProduction } = await import("./bind-production");
    const { bindProductionAuth } = await import("@repo/auth/di/bind-production");
    const { PayloadJobsEventBus } = await import("@repo/core-events");
    const { PayloadJobQueue } = await import("@repo/core-shared/jobs");

    await bindAllProduction(makeDeps());

    const args = vi.mocked(bindProductionAuth).mock.calls[0]!;
    // Args: (config, tracer, logger, bus, queue, realtime, realtimeRegistry)
    expect(args[3]).toBeInstanceOf(PayloadJobsEventBus);
    expect(args[4]).toBeInstanceOf(PayloadJobQueue);
  });

  it("forwards realtime and realtimeRegistry as 6th + 7th args to each production binder", async () => {
    const { RecordingRealtimeBroadcaster } = await import("@repo/core-testing/instrumentation");
    const { RealtimeHandlerRegistry } = await import("@repo/core-realtime");
    const { bindAllProduction } = await import("./bind-production");
    const { bindProductionAuth } = await import("@repo/auth/di/bind-production");

    const realtime = new RecordingRealtimeBroadcaster();
    const realtimeRegistry = new RealtimeHandlerRegistry();
    await bindAllProduction({ realtime, realtimeRegistry });

    const args = vi.mocked(bindProductionAuth).mock.calls[0]!;
    // Args: (config, tracer, logger, bus, queue, realtime, realtimeRegistry)
    expect(args[5]).toBe(realtime);
    expect(args[6]).toBe(realtimeRegistry);
  });
});

describe("bindAllDevSeed", () => {
  beforeEach(() => {
    vi.resetModules();
    vi.clearAllMocks();
  });

  it("passes an in-memory bus + queue to each per-feature dev-seed binder", async () => {
    const { bindAllDevSeed } = await import("./bind-production");
    const { bindDevSeedAuth } = await import("@repo/auth/di/bind-dev-seed");
    const { InMemoryEventBus } = await import("@repo/core-events");
    const { InMemoryJobQueue } = await import("@repo/core-shared/jobs");

    await bindAllDevSeed(makeDeps());

    const args = vi.mocked(bindDevSeedAuth).mock.calls[0]!;
    // Args: (tracer, logger, bus, queue, realtime, realtimeRegistry)
    expect(args[2]).toBeInstanceOf(InMemoryEventBus);
    expect(args[3]).toBeInstanceOf(InMemoryJobQueue);
  });

  it("forwards realtime and realtimeRegistry as 5th + 6th args to each dev-seed binder", async () => {
    const { RecordingRealtimeBroadcaster } = await import("@repo/core-testing/instrumentation");
    const { RealtimeHandlerRegistry } = await import("@repo/core-realtime");
    const { bindAllDevSeed } = await import("./bind-production");
    const { bindDevSeedAuth } = await import("@repo/auth/di/bind-dev-seed");

    const realtime = new RecordingRealtimeBroadcaster();
    const realtimeRegistry = new RealtimeHandlerRegistry();
    await bindAllDevSeed({ realtime, realtimeRegistry });

    const args = vi.mocked(bindDevSeedAuth).mock.calls[0]!;
    // Args: (tracer, logger, bus, queue, realtime, realtimeRegistry)
    expect(args[4]).toBe(realtime);
    expect(args[5]).toBe(realtimeRegistry);
  });
});

describe("bindAll dispatcher", () => {
  beforeEach(() => {
    vi.resetModules();
    vi.clearAllMocks();
    vi.unstubAllEnvs();
  });

  afterEach(() => {
    vi.unstubAllEnvs();
  });

  it("USE_DEV_SEED='true' wins → dispatches to bindAllDevSeed", async () => {
    vi.stubEnv("USE_DEV_SEED", "true");
    vi.stubEnv("NODE_ENV", "production"); // even in production, dev seed wins
    const { bindAll } = await import("./bind-production");
    const { bindDevSeedBlog } = await import("@repo/blog/di/bind-dev-seed");
    const { bindProductionBlog } = await import("@repo/blog/di/bind-production");

    await bindAll(makeDeps());

    expect(bindDevSeedBlog).toHaveBeenCalledOnce();
    expect(bindProductionBlog).not.toHaveBeenCalled();
  });

  it("NODE_ENV='production' (no override) → dispatches to bindAllProduction", async () => {
    vi.stubEnv("NODE_ENV", "production");
    const { bindAll } = await import("./bind-production");
    const { bindProductionBlog } = await import("@repo/blog/di/bind-production");
    const { bindDevSeedBlog } = await import("@repo/blog/di/bind-dev-seed");

    await bindAll(makeDeps());

    expect(bindProductionBlog).toHaveBeenCalledOnce();
    expect(bindDevSeedBlog).not.toHaveBeenCalled();
  });

  it("NODE_ENV='development' → dispatches to bindAllDevSeed (developer default)", async () => {
    vi.stubEnv("NODE_ENV", "development");
    const { bindAll } = await import("./bind-production");
    const { bindDevSeedBlog } = await import("@repo/blog/di/bind-dev-seed");
    const { bindProductionBlog } = await import("@repo/blog/di/bind-production");

    await bindAll(makeDeps());

    expect(bindDevSeedBlog).toHaveBeenCalledOnce();
    expect(bindProductionBlog).not.toHaveBeenCalled();
  });

  it("USE_DEV_SEED='false' is treated as not-set (only 'true' triggers dev seed)", async () => {
    vi.stubEnv("USE_DEV_SEED", "false");
    vi.stubEnv("NODE_ENV", "production");
    const { bindAll } = await import("./bind-production");
    const { bindProductionBlog } = await import("@repo/blog/di/bind-production");
    const { bindDevSeedBlog } = await import("@repo/blog/di/bind-dev-seed");

    await bindAll(makeDeps());

    expect(bindProductionBlog).toHaveBeenCalledOnce();
    expect(bindDevSeedBlog).not.toHaveBeenCalled();
  });
});

describe("realtime-ping registration (REALTIME_PING_DISABLED env-gate)", () => {
  beforeEach(() => {
    vi.resetModules();
    vi.clearAllMocks();
    vi.unstubAllEnvs();
  });

  afterEach(() => {
    vi.unstubAllEnvs();
  });

  it("registers realtime.ping when REALTIME_PING_DISABLED is unset (production)", async () => {
    const { bindAllProduction } = await import("./bind-production");
    const deps = makeDeps();
    await bindAllProduction(deps);
    expect(deps.realtimeRegistry.listChannels().map((d) => d.name)).toContain("realtime.ping");
  });

  it("registers realtime.ping when REALTIME_PING_DISABLED is unset (dev seed)", async () => {
    const { bindAllDevSeed } = await import("./bind-production");
    const deps = makeDeps();
    await bindAllDevSeed(deps);
    expect(deps.realtimeRegistry.listChannels().map((d) => d.name)).toContain("realtime.ping");
  });

  it("does NOT register realtime.ping when REALTIME_PING_DISABLED='true'", async () => {
    vi.stubEnv("REALTIME_PING_DISABLED", "true");
    const { bindAllProduction } = await import("./bind-production");
    const deps = makeDeps();
    await bindAllProduction(deps);
    expect(deps.realtimeRegistry.listChannels().map((d) => d.name)).not.toContain("realtime.ping");
  });

  it("treats REALTIME_PING_DISABLED='1' as not-disabled (only literal 'true' disables)", async () => {
    vi.stubEnv("REALTIME_PING_DISABLED", "1");
    const { bindAllDevSeed } = await import("./bind-production");
    const deps = makeDeps();
    await bindAllDevSeed(deps);
    expect(deps.realtimeRegistry.listChannels().map((d) => d.name)).toContain("realtime.ping");
  });
});

describe("bindAll instrumentation orthogonality (Rule 0, R47)", () => {
  beforeEach(() => {
    vi.resetModules();
    vi.clearAllMocks();
    vi.unstubAllEnvs();
  });

  afterEach(() => {
    vi.unstubAllEnvs();
  });

  it("DSN absent → bindNoopInstrumentation regardless of NODE_ENV (R48)", async () => {
    vi.stubEnv("WEB_NEXT_SENTRY_DSN", "");
    vi.stubEnv("NODE_ENV", "production");
    const { bindAll } = await import("./bind-production");
    const { bindNoopInstrumentation, bindSentryInstrumentation } = await import(
      "@repo/core-shared/instrumentation"
    );

    await bindAll(makeDeps());

    expect(bindNoopInstrumentation).toHaveBeenCalledOnce();
    expect(bindSentryInstrumentation).not.toHaveBeenCalled();
  });

  it("DSN set → bindSentryInstrumentation regardless of NODE_ENV", async () => {
    vi.stubEnv("WEB_NEXT_SENTRY_DSN", "https://x@y/1");
    vi.stubEnv("NODE_ENV", "development");
    const { bindAll } = await import("./bind-production");
    const { bindNoopInstrumentation, bindSentryInstrumentation } = await import(
      "@repo/core-shared/instrumentation"
    );

    await bindAll(makeDeps());

    expect(bindSentryInstrumentation).toHaveBeenCalledOnce();
    expect(bindNoopInstrumentation).not.toHaveBeenCalled();
  });

  it("Sentry instrumentation works alongside dev seed (USE_DEV_SEED=true)", async () => {
    vi.stubEnv("USE_DEV_SEED", "true");
    vi.stubEnv("WEB_NEXT_SENTRY_DSN", "https://x@y/1");
    const { bindAll } = await import("./bind-production");
    const { bindSentryInstrumentation } = await import("@repo/core-shared/instrumentation");
    const { bindDevSeedBlog } = await import("@repo/blog/di/bind-dev-seed");

    await bindAll(makeDeps());

    expect(bindSentryInstrumentation).toHaveBeenCalledOnce();
    expect(bindDevSeedBlog).toHaveBeenCalledOnce();
  });

  it("Noop instrumentation works alongside production binding (DSN unset, NODE_ENV=production)", async () => {
    vi.stubEnv("WEB_NEXT_SENTRY_DSN", "");
    vi.stubEnv("NODE_ENV", "production");
    const { bindAll } = await import("./bind-production");
    const { bindNoopInstrumentation } = await import("@repo/core-shared/instrumentation");
    const { bindProductionBlog } = await import("@repo/blog/di/bind-production");

    await bindAll(makeDeps());

    expect(bindNoopInstrumentation).toHaveBeenCalledOnce();
    expect(bindProductionBlog).toHaveBeenCalledOnce();
  });
});
