import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";

// Mutable state to control mock behavior per test
let dbSelectResult: unknown[] = [];
const mockEq = vi.fn((_col: unknown, _val: unknown) => ({ col: _col, val: _val }));
const mockDecryptSecret = vi.fn((s: string) => `decrypted:${s}`);

vi.mock("@groombook/db", () => {
  const authProviderConfig = new Proxy(
    { _name: "auth_provider_config" },
    {
      get(target, prop) {
        if (prop === "_name") return "auth_provider_config";
        if (prop === "$inferSelect") return {};
        return { table: "auth_provider_config", column: prop };
      },
    }
  );

  return {
    getDb: () => ({
      select: () => ({
        from: () => ({
          where: () => ({
            limit: () => dbSelectResult,
            [Symbol.iterator]: function* () {
              for (const item of dbSelectResult) yield item;
            },
            0: dbSelectResult[0],
            length: dbSelectResult.length,
          }),
        }),
      }),
    }),
    authProviderConfig,
    eq: mockEq,
    decryptSecret: mockDecryptSecret,
  };
});

async function reimportAuth() {
  vi.resetModules();
  vi.doMock("@groombook/db", () => ({
    getDb: () => ({
      select: () => ({
        from: () => ({
          where: () => ({
            limit: () => dbSelectResult,
            [Symbol.iterator]: function* () {
              for (const item of dbSelectResult) yield item;
            },
            0: dbSelectResult[0],
            length: dbSelectResult.length,
          }),
        }),
      }),
    }),
    authProviderConfig: {},
    eq: mockEq,
    decryptSecret: mockDecryptSecret,
  }));
  const mod = await import("../lib/auth.js");
  return mod;
}

describe("auth init", () => {
  const originalEnv = { ...process.env };

  beforeEach(() => {
    dbSelectResult = [];
    vi.clearAllMocks();
  });

  afterEach(() => {
    process.env = { ...originalEnv };
  });

  it("falls back to env vars when DB returns empty", async () => {
    process.env = {
      ...originalEnv,
      OIDC_ISSUER: "https://issuer.example.com",
      OIDC_CLIENT_ID: "test-client-id",
      OIDC_CLIENT_SECRET: "test-client-secret",
      BETTER_AUTH_SECRET: "test-secret",
      BETTER_AUTH_URL: "http://localhost:3000",
      NODE_ENV: "test",
    };

    const { initAuth, getAuth } = await reimportAuth();
    await initAuth();
    expect(getAuth()).toBeDefined();
  });

  it("uses DB config and decrypts clientSecret when DB has enabled provider", async () => {
    const dbConfig = {
      id: "config-id",
      providerId: "okta",
      displayName: "Okta",
      issuerUrl: "https://okta.example.com",
      internalBaseUrl: null,
      clientId: "okta-client-id",
      clientSecret: "encrypted:okta-secret",
      scopes: "openid profile email",
      enabled: true,
      createdAt: new Date(),
      updatedAt: new Date(),
    };
    dbSelectResult = [dbConfig];

    process.env = {
      ...originalEnv,
      BETTER_AUTH_SECRET: "test-secret",
      BETTER_AUTH_URL: "http://localhost:3000",
      NODE_ENV: "test",
    };

    const { initAuth, getAuth } = await reimportAuth();
    await initAuth();
    expect(getAuth()).toBeDefined();
    expect(mockDecryptSecret).toHaveBeenCalledWith("encrypted:okta-secret");
  });

  it("throws when BETTER_AUTH_SECRET is missing and AUTH_DISABLED is not set", async () => {
    process.env = {
      ...originalEnv,
      OIDC_ISSUER: "",
      OIDC_CLIENT_ID: "",
      OIDC_CLIENT_SECRET: "",
      NODE_ENV: "test",
    };
    delete process.env.BETTER_AUTH_SECRET;
    delete process.env.AUTH_DISABLED;

    const { initAuth } = await reimportAuth();
    await expect(initAuth()).rejects.toThrow(
      "[FATAL] BETTER_AUTH_SECRET environment variable is required when auth is enabled"
    );
  });

  it("builds placeholder auth when AUTH_DISABLED=true without throwing", async () => {
    process.env = {
      ...originalEnv,
      AUTH_DISABLED: "true",
      NODE_ENV: "test",
    };
    delete process.env.BETTER_AUTH_SECRET;

    const { initAuth, getAuth } = await reimportAuth();
    await expect(initAuth()).resolves.toBeUndefined();
    expect(getAuth()).toBeDefined();
  });
});
