import { DEFAULT_OPENAI_MODEL, VALUE_SCORE_WEIGHTS } from "@capakraken/shared";
import { z } from "zod";
import {
  isAiConfigured,
  parseAiError,
  sanitizeDiagnosticError,
} from "../ai-client.js";
import {
  RUNTIME_SECRET_FIELDS,
} from "../lib/system-settings-runtime.js";
import type { RuntimeSecretField, RuntimeSecretStatus } from "../lib/system-settings-runtime.js";
import { logger } from "../lib/logger.js";

/** Fields that must never appear in audit log values */
const SENSITIVE_FIELDS = new Set([
  "azureOpenAiApiKey",
  "smtpPassword",
  "azureDalleApiKey",
  "anonymizationSeed",
  "geminiApiKey",
]);

export const settingsUpdateInputSchema = z.object({
  aiProvider: z.enum(["openai", "azure"]).optional(),
  azureOpenAiEndpoint: z.string().url().optional().or(z.literal("")),
  azureOpenAiDeployment: z.string().optional(),
  azureOpenAiApiKey: z.string().optional(),
  azureApiVersion: z.string().optional(),
  aiMaxCompletionTokens: z.number().int().min(50).max(4000).optional(),
  aiTemperature: z.number().min(0).max(2).optional(),
  aiSummaryPrompt: z.string().optional(),
  scoreWeights: z.object({
    skillDepth: z.number().min(0).max(1),
    skillBreadth: z.number().min(0).max(1),
    costEfficiency: z.number().min(0).max(1),
    chargeability: z.number().min(0).max(1),
    experience: z.number().min(0).max(1),
  }).refine(
    (weights) => {
      const sum = weights.skillDepth
        + weights.skillBreadth
        + weights.costEfficiency
        + weights.chargeability
        + weights.experience;
      return Math.abs(sum - 1.0) < 0.01;
    },
    { message: "Score weights must sum to 1.0" },
  ).optional(),
  scoreVisibleRoles: z.array(z.enum(["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"])).optional(),
  smtpHost: z.string().optional(),
  smtpPort: z.number().int().min(1).max(65535).optional(),
  smtpUser: z.string().optional(),
  smtpPassword: z.string().optional(),
  smtpFrom: z.string().email().optional().or(z.literal("")),
  smtpTls: z.boolean().optional(),
  anonymizationEnabled: z.boolean().optional(),
  anonymizationDomain: z.string().trim().min(1).optional(),
  anonymizationSeed: z.string().trim().min(1).optional().or(z.literal("")),
  anonymizationMode: z.enum(["global"]).optional(),
  azureDalleDeployment: z.string().optional(),
  azureDalleEndpoint: z.string().url().optional().or(z.literal("")),
  azureDalleApiKey: z.string().optional(),
  geminiApiKey: z.string().optional(),
  geminiModel: z.string().optional(),
  imageProvider: z.enum(["dalle", "gemini"]).optional(),
  vacationDefaultDays: z.number().int().min(0).max(365).optional(),
  timelineUndoMaxSteps: z.number().int().min(1).max(200).optional(),
  requireMfaForRoles: z.array(z.enum(["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"])).nullable().optional(),
});

export type SettingsUpdateInput = z.infer<typeof settingsUpdateInputSchema>;

export function getDefaultScoreWeights() {
  return {
    skillDepth: VALUE_SCORE_WEIGHTS.SKILL_DEPTH,
    skillBreadth: VALUE_SCORE_WEIGHTS.SKILL_BREADTH,
    costEfficiency: VALUE_SCORE_WEIGHTS.COST_EFFICIENCY,
    chargeability: VALUE_SCORE_WEIGHTS.CHARGEABILITY,
    experience: VALUE_SCORE_WEIGHTS.EXPERIENCE,
  };
}

export function buildSystemSettingsViewModel(input: {
  settings: {
    aiProvider?: string | null;
    azureOpenAiEndpoint?: string | null;
    azureOpenAiDeployment?: string | null;
    azureApiVersion?: string | null;
    aiMaxCompletionTokens?: number | null;
    aiTemperature?: number | null;
    aiSummaryPrompt?: string | null;
    scoreWeights?: unknown;
    scoreVisibleRoles?: unknown;
    smtpHost?: string | null;
    smtpPort?: number | null;
    smtpUser?: string | null;
    smtpFrom?: string | null;
    smtpTls?: boolean | null;
    anonymizationEnabled?: boolean | null;
    anonymizationDomain?: string | null;
    anonymizationMode?: string | null;
    azureDalleDeployment?: string | null;
    azureDalleEndpoint?: string | null;
    geminiModel?: string | null;
    imageProvider?: string | null;
    vacationDefaultDays?: number | null;
    timelineUndoMaxSteps?: number | null;
    requireMfaForRoles?: unknown;
  } | null | undefined;
  runtimeSettings: {
    azureOpenAiApiKey?: string | null;
    smtpPassword?: string | null;
    azureDalleApiKey?: string | null;
    geminiApiKey?: string | null;
  } | null | undefined;
  runtimeSecrets: Record<RuntimeSecretField, RuntimeSecretStatus>;
  defaultSummaryPrompt: string;
}) {
  const defaultWeights = getDefaultScoreWeights();
  const settings = input.settings;

  return {
    aiProvider: settings?.aiProvider ?? "openai",
    azureOpenAiEndpoint: settings?.azureOpenAiEndpoint ?? null,
    azureOpenAiDeployment: settings?.azureOpenAiDeployment ?? null,
    azureApiVersion: settings?.azureApiVersion ?? "2025-01-01-preview",
    aiMaxCompletionTokens: settings?.aiMaxCompletionTokens ?? 300,
    aiTemperature: settings?.aiTemperature ?? 1,
    aiSummaryPrompt: settings?.aiSummaryPrompt ?? null,
    defaultSummaryPrompt: input.defaultSummaryPrompt,
    hasApiKey: !!input.runtimeSettings?.azureOpenAiApiKey,
    runtimeSecrets: input.runtimeSecrets,
    legacyStoredSecretFields: RUNTIME_SECRET_FIELDS.filter(
      (field) => input.runtimeSecrets[field].hasStoredValue,
    ),
    scoreWeights: (settings?.scoreWeights as typeof defaultWeights | null | undefined) ?? defaultWeights,
    scoreVisibleRoles: (settings?.scoreVisibleRoles as string[] | null | undefined) ?? ["ADMIN", "MANAGER"],
    smtpHost: settings?.smtpHost ?? null,
    smtpPort: settings?.smtpPort ?? 587,
    smtpUser: settings?.smtpUser ?? null,
    smtpFrom: settings?.smtpFrom ?? null,
    smtpTls: settings?.smtpTls ?? true,
    hasSmtpPassword: !!input.runtimeSettings?.smtpPassword,
    anonymizationEnabled: settings?.anonymizationEnabled ?? false,
    anonymizationDomain: settings?.anonymizationDomain ?? "superhartmut.de",
    anonymizationMode: settings?.anonymizationMode ?? "global",
    azureDalleDeployment: settings?.azureDalleDeployment ?? null,
    azureDalleEndpoint: settings?.azureDalleEndpoint ?? null,
    hasDalleApiKey: !!input.runtimeSettings?.azureDalleApiKey,
    geminiModel: settings?.geminiModel ?? "gemini-2.5-flash-image",
    hasGeminiApiKey: !!input.runtimeSettings?.geminiApiKey,
    imageProvider: settings?.imageProvider ?? "dalle",
    vacationDefaultDays: settings?.vacationDefaultDays ?? 28,
    timelineUndoMaxSteps: settings?.timelineUndoMaxSteps ?? 50,
    requireMfaForRoles: (settings?.requireMfaForRoles as string[] | null | undefined) ?? null,
  };
}

export function buildSettingsUpdatePayload(input: SettingsUpdateInput): {
  data: Record<string, unknown>;
  ignoredSecretFields: string[];
} {
  const data: Record<string, unknown> = {};
  const ignoredSecretFields: string[] = [];

  if (input.aiProvider !== undefined) data.aiProvider = input.aiProvider;
  if (input.azureOpenAiEndpoint !== undefined) data.azureOpenAiEndpoint = input.azureOpenAiEndpoint || null;
  if (input.azureOpenAiDeployment !== undefined) data.azureOpenAiDeployment = input.azureOpenAiDeployment || null;
  if (input.azureOpenAiApiKey !== undefined) ignoredSecretFields.push("azureOpenAiApiKey");
  if (input.azureApiVersion !== undefined) data.azureApiVersion = input.azureApiVersion || null;
  if (input.aiMaxCompletionTokens !== undefined) data.aiMaxCompletionTokens = input.aiMaxCompletionTokens;
  if (input.aiTemperature !== undefined) data.aiTemperature = input.aiTemperature;
  if (input.aiSummaryPrompt !== undefined) data.aiSummaryPrompt = input.aiSummaryPrompt || null;
  if (input.scoreWeights !== undefined) data.scoreWeights = input.scoreWeights;
  if (input.scoreVisibleRoles !== undefined) data.scoreVisibleRoles = input.scoreVisibleRoles;

  if (input.smtpHost !== undefined) data.smtpHost = input.smtpHost || null;
  if (input.smtpPort !== undefined) data.smtpPort = input.smtpPort;
  if (input.smtpUser !== undefined) data.smtpUser = input.smtpUser || null;
  if (input.smtpPassword !== undefined) ignoredSecretFields.push("smtpPassword");
  if (input.smtpFrom !== undefined) data.smtpFrom = input.smtpFrom || null;
  if (input.smtpTls !== undefined) data.smtpTls = input.smtpTls;

  if (input.anonymizationEnabled !== undefined) data.anonymizationEnabled = input.anonymizationEnabled;
  if (input.anonymizationDomain !== undefined) data.anonymizationDomain = input.anonymizationDomain || "superhartmut.de";
  if (input.anonymizationSeed !== undefined) ignoredSecretFields.push("anonymizationSeed");
  if (input.anonymizationMode !== undefined) {
    data.anonymizationMode = input.anonymizationMode;
    data.anonymizationAliases = null;
  }

  if (input.azureDalleDeployment !== undefined) data.azureDalleDeployment = input.azureDalleDeployment || null;
  if (input.azureDalleEndpoint !== undefined) data.azureDalleEndpoint = input.azureDalleEndpoint || null;
  if (input.azureDalleApiKey !== undefined) ignoredSecretFields.push("azureDalleApiKey");

  if (input.geminiApiKey !== undefined) ignoredSecretFields.push("geminiApiKey");
  if (input.geminiModel !== undefined) data.geminiModel = input.geminiModel || null;

  if (input.imageProvider !== undefined) data.imageProvider = input.imageProvider;
  if (input.vacationDefaultDays !== undefined) data.vacationDefaultDays = input.vacationDefaultDays;
  if (input.timelineUndoMaxSteps !== undefined) data.timelineUndoMaxSteps = input.timelineUndoMaxSteps;
  if (input.requireMfaForRoles !== undefined) data.requireMfaForRoles = input.requireMfaForRoles ?? null;

  return { data, ignoredSecretFields };
}

export function sanitizeSettingsAuditSnapshot(obj: Record<string, unknown>): Record<string, unknown> {
  const result: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(obj)) {
    result[key] = SENSITIVE_FIELDS.has(key) ? (value ? "***" : null) : value;
  }
  return result;
}

export async function testRuntimeAiConnection(settings: {
  aiProvider?: string | null;
  azureOpenAiEndpoint?: string | null;
  azureOpenAiDeployment?: string | null;
  azureOpenAiApiKey?: string | null;
  azureApiVersion?: string | null;
} | null | undefined): Promise<{ ok: boolean; error?: string }> {
  if (!isAiConfigured(settings)) {
    const provider = settings?.aiProvider ?? "openai";
    if (provider === "azure") {
      return { ok: false, error: "Missing required fields: endpoint, deployment name, and API key are all required for Azure OpenAI." };
    }
    return { ok: false, error: "Missing required fields: model name and API key are required." };
  }

  const configuredSettings = settings as NonNullable<typeof settings>;
  const provider = configuredSettings.aiProvider ?? "openai";
  const apiKey = configuredSettings.azureOpenAiApiKey!;

  if (provider === "azure") {
    const endpoint = configuredSettings.azureOpenAiEndpoint!.replace(/\/$/, "");
    const deployment = configuredSettings.azureOpenAiDeployment!;
    const apiVersion = configuredSettings.azureApiVersion ?? "2025-01-01-preview";
    const url = `${endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${apiVersion}`;
    return performAiConnectionFetch(provider, url, {
      "Content-Type": "application/json",
      "api-key": apiKey,
    });
  }

  const model = configuredSettings.azureOpenAiDeployment ?? DEFAULT_OPENAI_MODEL;
  return performAiConnectionFetch(
    provider,
    "https://api.openai.com/v1/chat/completions",
    {
      "Content-Type": "application/json",
      Authorization: `Bearer ${apiKey}`,
    },
    { model },
  );
}

async function performAiConnectionFetch(
  provider: string,
  url: string,
  headers: Record<string, string>,
  bodyOverrides: Record<string, unknown> = {},
): Promise<{ ok: boolean; error?: string }> {
  try {
    const response = await fetch(url, {
      method: "POST",
      headers,
      body: JSON.stringify({
        messages: [{ role: "user", content: "ping" }],
        max_completion_tokens: 5,
        ...bodyOverrides,
      }),
    });

    const body = await response.text();
    if (response.ok) {
      return { ok: true };
    }

    let message = body;
    try {
      const parsed = JSON.parse(body) as { error?: { message?: string } };
      if (parsed.error?.message) {
        message = parsed.error.message;
      }
    } catch {
      message = body;
    }

    const raw = `HTTP ${response.status}: ${message}`;
    logger.warn(
      { provider, diagnostic: sanitizeDiagnosticError(raw) },
      "AI connection test failed",
    );
    return { ok: false, error: parseAiError(new Error(raw)) };
  } catch (error) {
    logger.warn(
      { provider, diagnostic: sanitizeDiagnosticError(error) },
      "AI connection test failed",
    );
    return { ok: false, error: parseAiError(error) };
  }
}
