import {
  withSpan,
  withCapture,
  INSTRUMENTATION_SYMBOLS,
  type ITracer,
  type ILogger,
} from "@repo/core-shared/instrumentation";
import type { BindContext } from "@repo/core-shared/di";
import {
  assertFeatureConformance,
  wireUseCase,
} from "@repo/core-shared/conformance";
import { NoopRateLimit } from "@repo/core-shared/rate-limit";
import { authManifest } from "../feature.manifest.js";
import { authContainer } from "./container.js";
import { AUTH_SYMBOLS } from "./symbols.js";
import { MockUsersRepository } from "../infrastructure/repositories/users.repository.mock.js";
import { buildDevUsers } from "../__seeds__/dev.js";
import { signInUseCase } from "../application/use-cases/sign-in.use-case.js";
import { signUpUseCase } from "../application/use-cases/sign-up.use-case.js";
import { signOutUseCase } from "../application/use-cases/sign-out.use-case.js";
import { signInController } from "../interface-adapters/controllers/sign-in.controller.js";
import { signUpController } from "../interface-adapters/controllers/sign-up.controller.js";
import { signOutController } from "../interface-adapters/controllers/sign-out.controller.js";
import type { IUsersRepository } from "../application/repositories/users.repository.interface.js";
import type { IAuthenticationService } from "../application/services/authentication.service.interface.js";

/**
 * Replace the default mock with a populated one for dev mode + storybook.
 *
 * Call this from app boot when `USE_DEV_SEED=true`, mutually exclusive with
 * `bindProductionAuth(config)`. Tests must NOT call this — they construct
 * `new MockUsersRepository()` directly and seed via factories per-test.
 *
 * The `IAuthenticationService` binding is left untouched; it resolves users
 * through DI from the newly seeded repo.
 *
 * Idempotent: safe to call multiple times; each call rebuilds a fresh
 * populated repo and rebinds the symbol.
 */
export async function bindDevSeedAuth(ctx: BindContext): Promise<void> {
  const {
    tracer,
    logger,
    bus,
    queue,
    realtime,
    realtimeRegistry,
    consentFactory,
  } = ctx;

  // Bind shared instrumentation into feature container
  if (authContainer.isBound(INSTRUMENTATION_SYMBOLS.TRACER)) {
    authContainer.unbind(INSTRUMENTATION_SYMBOLS.TRACER);
  }
  if (authContainer.isBound(INSTRUMENTATION_SYMBOLS.LOGGER)) {
    authContainer.unbind(INSTRUMENTATION_SYMBOLS.LOGGER);
  }
  authContainer
    .bind<ITracer>(INSTRUMENTATION_SYMBOLS.TRACER)
    .toConstantValue(tracer);
  authContainer
    .bind<ILogger>(INSTRUMENTATION_SYMBOLS.LOGGER)
    .toConstantValue(logger);

  if (authContainer.isBound(AUTH_SYMBOLS.IUsersRepository)) {
    authContainer.unbind(AUTH_SYMBOLS.IUsersRepository);
  }

  const repo = new MockUsersRepository([], tracer, logger);
  for (const user of buildDevUsers()) {
    await repo.createUser(user);
  }

  authContainer
    .bind<IUsersRepository>(AUTH_SYMBOLS.IUsersRepository)
    .toConstantValue(repo);

  // Need auth service from container for use cases
  const authService = authContainer.get<IAuthenticationService>(
    AUTH_SYMBOLS.IAuthenticationService,
  );

  // Use cases
  const wrappedSignIn = wireUseCase({
    container: authContainer,
    symbol: AUTH_SYMBOLS.ISignInUseCase,
    factory: signInUseCase,
    deps: [repo, authService, ctx.rateLimit ?? new NoopRateLimit()],
    feature: "auth",
    layer: "use-case",
    name: "signIn",
    tracer,
    logger,
    rateLimit: ctx.rateLimit ?? new NoopRateLimit(),
  });
  const wrappedSignUp = wireUseCase({
    container: authContainer,
    symbol: AUTH_SYMBOLS.ISignUpUseCase,
    factory: signUpUseCase,
    deps: [repo, authService, bus, consentFactory],
    feature: "auth",
    layer: "use-case",
    name: "signUp",
    tracer,
    logger,
  });
  const wrappedSignOut = wireUseCase({
    container: authContainer,
    symbol: AUTH_SYMBOLS.ISignOutUseCase,
    factory: signOutUseCase,
    deps: [authService],
    feature: "auth",
    layer: "use-case",
    name: "signOut",
    tracer,
    logger,
  });

  // Controllers
  for (const sym of [
    AUTH_SYMBOLS.ISignInController,
    AUTH_SYMBOLS.ISignUpController,
    AUTH_SYMBOLS.ISignOutController,
  ]) {
    if (authContainer.isBound(sym)) authContainer.unbind(sym);
  }
  authContainer
    .bind(AUTH_SYMBOLS.ISignInController)
    .toConstantValue(
      withSpan(
        tracer,
        { name: "auth.signIn", op: "controller" },
        withCapture(
          logger,
          { feature: "auth", layer: "controller", name: "auth.signIn" },
          signInController(wrappedSignIn),
        ),
      ),
    );
  authContainer
    .bind(AUTH_SYMBOLS.ISignUpController)
    .toConstantValue(
      withSpan(
        tracer,
        { name: "auth.signUp", op: "controller" },
        withCapture(
          logger,
          { feature: "auth", layer: "controller", name: "auth.signUp" },
          signUpController(wrappedSignUp),
        ),
      ),
    );
  authContainer
    .bind(AUTH_SYMBOLS.ISignOutController)
    .toConstantValue(
      withSpan(
        tracer,
        { name: "auth.signOut", op: "controller" },
        withCapture(
          logger,
          { feature: "auth", layer: "controller", name: "auth.signOut" },
          signOutController(wrappedSignOut),
        ),
      ),
    );
  // bus + queue are passed through; generated handlers consume them at the anchors below.
  void bus;
  void queue;
  void realtime;
  void realtimeRegistry;
  // <gen:event-handlers>
  // <gen:jobs>
  // <gen:realtime-handlers>

  // Boot-time conformance check (dev-seed mode).
  assertFeatureConformance(
    authContainer,
    authManifest,
    {
      signIn: AUTH_SYMBOLS.ISignInUseCase,
      signUp: AUTH_SYMBOLS.ISignUpUseCase,
      signOut: AUTH_SYMBOLS.ISignOutUseCase,
    },
    ctx,
  );
}
