import { SystemRole } from "@capakraken/shared";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { parseAiError } from "../ai-client.js";
import { logger } from "../lib/logger.js";
import { settingsRouter } from "../router/settings.js";
import { createCallerFactory } from "../trpc.js";

vi.mock("../lib/logger.js", () => ({
  logger: {
    error: vi.fn(),
    warn: vi.fn(),
    info: vi.fn(),
    debug: vi.fn(),
  },
}));

const createCaller = createCallerFactory(settingsRouter);

function createAdminCaller(db: Record<string, unknown>) {
  return createCaller({
    session: {
      user: { email: "admin@example.com", name: "Admin", image: null },
      expires: "2099-01-01T00:00:00.000Z",
    },
    db: db as never,
    dbUser: {
      id: "admin_1",
      systemRole: SystemRole.ADMIN,
      permissionOverrides: null,
    },
  });
}

describe("runtime config hardening", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

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

  it("sanitizes AI fallback diagnostics", () => {
    const error = parseAiError(
      new Error(
        "Provider failed at https://example.openai.azure.com/path?api-key=topsecret with Bearer sk-super-secret",
      ),
    );

    expect(error).toContain("<redacted-url>");
    expect(error).toContain("<redacted-secret>");
    expect(error).not.toContain("https://example.openai.azure.com");
    expect(error).not.toContain("sk-super-secret");
    expect(error).not.toContain("topsecret");
  });

  it("does not expose raw diagnostics from AI connection tests", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      aiProvider: "openai",
      azureOpenAiDeployment: "gpt-4o-mini",
      azureOpenAiApiKey: "sk-live-secret",
    });

    vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
      ok: false,
      status: 500,
      text: vi.fn().mockResolvedValue(
        JSON.stringify({
          error: {
            message: "upstream failure at https://api.openai.com/v1/chat/completions?api-key=hidden",
          },
        }),
      ),
    }));

    const caller = createAdminCaller({
      systemSettings: {
        findUnique,
      },
    });

    const result = await caller.testAiConnection();

    expect(result.ok).toBe(false);
    expect(result).not.toHaveProperty("raw");
    expect(result.error).toContain("<redacted-url>");
    expect(result.error).not.toContain("https://api.openai.com");
    expect(result.error).not.toContain("hidden");

    expect(logger.warn).toHaveBeenCalledWith(
      expect.objectContaining({
        provider: "openai",
        diagnostic: expect.stringContaining("<redacted-url>"),
      }),
      "AI connection test failed",
    );
  });

  it("reports secret presence flags when secrets come from environment overrides", async () => {
    vi.stubEnv("OPENAI_API_KEY", "env-openai-key");
    vi.stubEnv("SMTP_PASSWORD", "env-smtp-password");
    vi.stubEnv("GEMINI_API_KEY", "env-gemini-key");

    const caller = createAdminCaller({
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue({
          aiProvider: "openai",
          azureOpenAiApiKey: null,
          smtpPassword: null,
          geminiApiKey: null,
        }),
      },
    });

    const result = await caller.getSystemSettings();

    expect(result.hasApiKey).toBe(true);
    expect(result.hasSmtpPassword).toBe(true);
    expect(result.hasGeminiApiKey).toBe(true);
    expect(result.runtimeSecrets.azureOpenAiApiKey.activeSource).toBe("environment");
    expect(result.runtimeSecrets.smtpPassword.activeSource).toBe("environment");
    expect(result.legacyStoredSecretFields).toEqual([]);
  });

  it("prefers environment API keys during AI connection tests", async () => {
    vi.stubEnv("OPENAI_API_KEY", "env-openai-key");
    const fetch = vi.fn().mockResolvedValue({
      ok: true,
      text: vi.fn().mockResolvedValue("ok"),
    });
    vi.stubGlobal("fetch", fetch);

    const caller = createAdminCaller({
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue({
          aiProvider: "openai",
          azureOpenAiDeployment: "gpt-4o-mini",
          azureOpenAiApiKey: null,
        }),
      },
    });

    const result = await caller.testAiConnection();

    expect(result).toEqual({ ok: true });
    expect(fetch).toHaveBeenCalledWith(
      "https://api.openai.com/v1/chat/completions",
      expect.objectContaining({
        headers: expect.objectContaining({
          Authorization: "Bearer env-openai-key",
        }),
      }),
    );
  });

  it("does not persist incoming secret fields through updateSystemSettings", async () => {
    const findUnique = vi.fn().mockResolvedValue(null);
    const upsert = vi.fn().mockResolvedValue({});
    const caller = createAdminCaller({
      systemSettings: {
        findUnique,
        upsert,
      },
    });

    const result = await caller.updateSystemSettings({
      azureOpenAiApiKey: "sk-should-not-store",
      smtpPassword: "smtp-should-not-store",
      geminiApiKey: "gemini-should-not-store",
      azureDalleApiKey: "dalle-should-not-store",
      anonymizationSeed: "seed-should-not-store",
      aiProvider: "openai",
      azureOpenAiDeployment: "gpt-4o-mini",
    });

    expect(result).toEqual({
      ok: true,
      ignoredSecretFields: [
        "azureOpenAiApiKey",
        "smtpPassword",
        "anonymizationSeed",
        "azureDalleApiKey",
        "geminiApiKey",
      ],
      secretStorageMode: "environment-only",
    });
    expect(upsert).toHaveBeenCalledWith({
      where: { id: "singleton" },
      create: {
        id: "singleton",
        aiProvider: "openai",
        azureOpenAiDeployment: "gpt-4o-mini",
      },
      update: {
        aiProvider: "openai",
        azureOpenAiDeployment: "gpt-4o-mini",
      },
    });
  });

  it("can clear legacy runtime secrets from database storage", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      azureOpenAiApiKey: "db-key",
      azureDalleApiKey: null,
      geminiApiKey: "db-gemini",
      smtpPassword: "db-smtp",
      anonymizationSeed: null,
    });
    const update = vi.fn().mockResolvedValue({});
    const caller = createAdminCaller({
      systemSettings: {
        findUnique,
        update,
      },
    });

    const result = await caller.clearStoredRuntimeSecrets();

    expect(result).toEqual({
      ok: true,
      clearedFields: ["azureOpenAiApiKey", "geminiApiKey", "smtpPassword"],
    });
    expect(update).toHaveBeenCalledWith({
      where: { id: "singleton" },
      data: {
        azureOpenAiApiKey: null,
        geminiApiKey: null,
        smtpPassword: null,
      },
    });
  });
});
