import { SystemRole } from "@nexus/shared";
import { describe, expect, it, vi, beforeEach } from "vitest";
import { settingsRouter } from "../router/settings.js";
import { createCallerFactory } from "../trpc.js";

// ─── Hoisted mocks ────────────────────────────────────────────────────────────

const { runPruning } = vi.hoisted(() => ({
  runPruning: vi.fn(),
}));

const { testSmtpConnection } = vi.hoisted(() => ({
  testSmtpConnection: vi.fn(),
}));

vi.mock("../lib/pruning.js", () => ({ runPruning }));

vi.mock("../lib/email.js", () => ({ testSmtpConnection }));

vi.mock("../lib/audit.js", () => ({
  createAuditEntry: vi.fn().mockResolvedValue(undefined),
}));

// ai-client mock — isAiConfigured drives getAiConfigured and testAiConnection
vi.mock("../ai-client.js", () => ({
  isAiConfigured: vi.fn().mockReturnValue(false),
  parseAiError: vi.fn().mockReturnValue("AI connection error"),
  sanitizeDiagnosticError: vi.fn().mockReturnValue("sanitized"),
}));

// ─── Caller factory ───────────────────────────────────────────────────────────

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: "user_admin",
      systemRole: SystemRole.ADMIN,
      permissionOverrides: null,
    },
    roleDefaults: null,
  });
}

function createManagerCaller(db: Record<string, unknown>) {
  return createCaller({
    session: {
      user: { email: "manager@example.com", name: "Manager", image: null },
      expires: "2099-01-01T00:00:00.000Z",
    },
    db: db as never,
    dbUser: {
      id: "user_manager",
      systemRole: SystemRole.MANAGER,
      permissionOverrides: null,
    },
    roleDefaults: null,
  });
}

// ─── Tests ────────────────────────────────────────────────────────────────────

describe("settings.getSystemSettings", () => {
  it("returns system settings view with defaults when no record exists", async () => {
    const findUnique = vi.fn().mockResolvedValue(null);
    const caller = createAdminCaller({ systemSettings: { findUnique } });

    const result = await caller.getSystemSettings();

    expect(findUnique).toHaveBeenCalledWith({ where: { id: "singleton" } });
    expect(result).toMatchObject({
      aiProvider: "openai",
      smtpPort: 587,
      anonymizationEnabled: false,
      vacationDefaultDays: 28,
    });
  });

  it("returns stored settings when a singleton record exists", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      aiProvider: "azure",
      smtpHost: "mail.example.com",
      smtpPort: 465,
      smtpUser: "user@example.com",
      smtpFrom: "noreply@example.com",
      smtpTls: true,
      anonymizationEnabled: true,
      vacationDefaultDays: 30,
    });
    const caller = createAdminCaller({ systemSettings: { findUnique } });

    const result = await caller.getSystemSettings();

    expect(result).toMatchObject({
      aiProvider: "azure",
      smtpHost: "mail.example.com",
      smtpPort: 465,
      anonymizationEnabled: true,
      vacationDefaultDays: 30,
    });
  });

  it("rejects non-admin callers with FORBIDDEN", async () => {
    const caller = createManagerCaller({ systemSettings: { findUnique: vi.fn() } });

    await expect(caller.getSystemSettings()).rejects.toMatchObject({
      code: "FORBIDDEN",
    });
  });
});

describe("settings.updateSystemSettings", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("upserts settings and returns ok with ignored secret fields", async () => {
    const findUnique = vi.fn().mockResolvedValue(null);
    const upsert = vi.fn().mockResolvedValue({ id: "singleton" });
    const caller = createAdminCaller({
      systemSettings: { findUnique, upsert },
    });

    const result = await caller.updateSystemSettings({
      aiProvider: "openai",
      azureOpenAiApiKey: "secret-key", // should be ignored
      vacationDefaultDays: 25,
    });

    expect(result.ok).toBe(true);
    expect(result.ignoredSecretFields).toContain("azureOpenAiApiKey");
    expect(upsert).toHaveBeenCalled();
  });

  it("returns ok without DB write when only secret fields are supplied", async () => {
    const findUnique = vi.fn();
    const upsert = vi.fn();
    const caller = createAdminCaller({
      systemSettings: { findUnique, upsert },
    });

    const result = await caller.updateSystemSettings({
      azureOpenAiApiKey: "secret",
      smtpPassword: "password",
    });

    expect(result.ok).toBe(true);
    expect(result.ignoredSecretFields).toContain("azureOpenAiApiKey");
    expect(result.ignoredSecretFields).toContain("smtpPassword");
    // No DB write because only secret fields were provided
    expect(upsert).not.toHaveBeenCalled();
  });

  it("rejects non-admin callers with FORBIDDEN", async () => {
    const caller = createManagerCaller({ systemSettings: { upsert: vi.fn() } });

    await expect(caller.updateSystemSettings({ vacationDefaultDays: 20 })).rejects.toMatchObject({
      code: "FORBIDDEN",
    });
  });

  it("rejects invalid score weights that do not sum to 1.0", async () => {
    const caller = createAdminCaller({ systemSettings: {} });

    await expect(
      caller.updateSystemSettings({
        scoreWeights: {
          skillDepth: 0.5,
          skillBreadth: 0.5,
          costEfficiency: 0.5,
          chargeability: 0.5,
          experience: 0.5,
        },
      }),
    ).rejects.toThrow();
  });
});

describe("settings.clearStoredRuntimeSecrets", () => {
  it("returns empty clearedFields when no secrets are stored", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      azureOpenAiApiKey: null,
      azureDalleApiKey: null,
      geminiApiKey: null,
      smtpPassword: null,
      anonymizationSeed: null,
    });
    const caller = createAdminCaller({ systemSettings: { findUnique } });

    const result = await caller.clearStoredRuntimeSecrets();

    expect(result.ok).toBe(true);
    expect(result.clearedFields).toHaveLength(0);
  });

  it("clears fields that have stored values and returns their names", async () => {
    const findUnique = vi.fn().mockResolvedValue({
      azureOpenAiApiKey: "stored-key",
      azureDalleApiKey: null,
      geminiApiKey: "gemini-key",
      smtpPassword: null,
      anonymizationSeed: null,
    });
    const update = vi.fn().mockResolvedValue({ id: "singleton" });
    const caller = createAdminCaller({ systemSettings: { findUnique, update } });

    const result = await caller.clearStoredRuntimeSecrets();

    expect(result.ok).toBe(true);
    expect(result.clearedFields).toContain("azureOpenAiApiKey");
    expect(result.clearedFields).toContain("geminiApiKey");
    expect(result.clearedFields).not.toContain("smtpPassword");
    expect(update).toHaveBeenCalled();
  });

  it("rejects non-admin callers with FORBIDDEN", async () => {
    const caller = createManagerCaller({ systemSettings: { findUnique: vi.fn() } });

    await expect(caller.clearStoredRuntimeSecrets()).rejects.toMatchObject({
      code: "FORBIDDEN",
    });
  });
});

describe("settings.getAiConfigured", () => {
  it("returns configured: false when AI is not configured", async () => {
    const { isAiConfigured } = await import("../ai-client.js");
    vi.mocked(isAiConfigured).mockReturnValue(false);

    const findUnique = vi.fn().mockResolvedValue(null);
    const caller = createAdminCaller({ systemSettings: { findUnique } });

    const result = await caller.getAiConfigured();

    expect(result.configured).toBe(false);
  });

  it("returns configured: true when AI settings are present", async () => {
    const { isAiConfigured } = await import("../ai-client.js");
    vi.mocked(isAiConfigured).mockReturnValue(true);

    const findUnique = vi.fn().mockResolvedValue({
      aiProvider: "openai",
      azureOpenAiApiKey: "sk-test",
    });
    const caller = createAdminCaller({ systemSettings: { findUnique } });

    const result = await caller.getAiConfigured();

    expect(result.configured).toBe(true);
  });
});

describe("settings.runPruning", () => {
  beforeEach(() => {
    runPruning.mockReset();
  });

  it("delegates to runPruning and returns its result", async () => {
    runPruning.mockResolvedValue({
      inviteTokensDeleted: 3,
      passwordResetTokensDeleted: 1,
      notificationsDeleted: 20,
    });

    const caller = createAdminCaller({ inviteToken: {}, passwordResetToken: {}, notification: {} });

    const result = await caller.runPruning();

    expect(runPruning).toHaveBeenCalled();
    expect(result).toMatchObject({
      inviteTokensDeleted: 3,
      passwordResetTokensDeleted: 1,
      notificationsDeleted: 20,
    });
  });

  it("rejects non-admin callers with FORBIDDEN", async () => {
    const caller = createManagerCaller({});

    await expect(caller.runPruning()).rejects.toMatchObject({ code: "FORBIDDEN" });
  });
});

describe("settings.testSmtpConnection", () => {
  beforeEach(() => {
    testSmtpConnection.mockReset();
  });

  it("returns success result when SMTP connection succeeds", async () => {
    testSmtpConnection.mockResolvedValue({ ok: true });

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

    const result = await caller.testSmtpConnection();

    expect(result.ok).toBe(true);
  });

  it("returns failure result when SMTP connection fails", async () => {
    testSmtpConnection.mockResolvedValue({ ok: false, error: "Connection refused" });

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

    const result = await caller.testSmtpConnection();

    expect(result.ok).toBe(false);
  });
});
