import "reflect-metadata";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { NoopTracer, NoopLogger } from "@repo/core-shared/instrumentation";
import { RecordingEventBus, RecordingJobQueue } from "@repo/core-testing/instrumentation";
import { bindDevSeedAuth } from "@/di/bind-dev-seed";
import { authContainer } from "@/di/container";
import { AUTH_SYMBOLS } from "@/di/symbols";
import { MockUsersRepository } from "@/infrastructure/repositories/users.repository.mock";
import type { IUsersRepository } from "@/application/repositories/users.repository.interface";

const noop = { tracer: new NoopTracer(), logger: new NoopLogger() };

describe("bindDevSeedAuth", () => {
  // Each test starts from the default empty-mock binding and tears down
  // afterwards so the global authContainer state stays clean for siblings.
  beforeEach(() => {
    if (authContainer.isBound(AUTH_SYMBOLS.IUsersRepository)) {
      authContainer.unbind(AUTH_SYMBOLS.IUsersRepository);
    }
    authContainer
      .bind<IUsersRepository>(AUTH_SYMBOLS.IUsersRepository)
      .to(MockUsersRepository);
  });

  afterEach(() => {
    if (authContainer.isBound(AUTH_SYMBOLS.IUsersRepository)) {
      authContainer.unbind(AUTH_SYMBOLS.IUsersRepository);
    }
    authContainer
      .bind<IUsersRepository>(AUTH_SYMBOLS.IUsersRepository)
      .to(MockUsersRepository);
  });

  it("populates the repository with the dev users", async () => {
    await bindDevSeedAuth({ ...noop, bus: new RecordingEventBus(), queue: new RecordingJobQueue() });

    const repo = authContainer.get<IUsersRepository>(
      AUTH_SYMBOLS.IUsersRepository,
    );
    const alice = await repo.getUserByUsername("alice");
    const bob = await repo.getUserByUsername("bob");

    expect(alice).toBeDefined();
    expect(bob).toBeDefined();
  });

  it("seeds alice reachable by username", async () => {
    await bindDevSeedAuth({ ...noop, bus: new RecordingEventBus(), queue: new RecordingJobQueue() });

    const repo = authContainer.get<IUsersRepository>(
      AUTH_SYMBOLS.IUsersRepository,
    );
    const alice = await repo.getUserByUsername("alice");

    expect(alice).toBeDefined();
    expect(alice?.id).toBe("alice");
    expect(alice?.passwordHash).toBe("hashed_secret_alice");
  });

  it("is idempotent — calling twice rebuilds a fresh populated repo", async () => {
    await bindDevSeedAuth({ ...noop, bus: new RecordingEventBus(), queue: new RecordingJobQueue() });
    const before = authContainer.get<IUsersRepository>(
      AUTH_SYMBOLS.IUsersRepository,
    );
    const beforeAlice = await before.getUserByUsername("alice");

    await bindDevSeedAuth({ ...noop, bus: new RecordingEventBus(), queue: new RecordingJobQueue() });
    const after = authContainer.get<IUsersRepository>(
      AUTH_SYMBOLS.IUsersRepository,
    );
    const afterAlice = await after.getUserByUsername("alice");

    expect(afterAlice?.username).toBe(beforeAlice?.username);
    // It's a fresh instance — not the previous one.
    expect(after).not.toBe(before);
  });
});
