import { app, safeStorage } from "electron";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import type {
  AiContentModelMap,
  AiContentType,
  AiSettings,
  AiSettingsPatch
} from "../../packages/shared/src";

type StoredAiSettings = {
  baseUrl: string;
  model: string;
  contentModels: AiContentModelMap;
  promptPackId: string;
  disabledPersonalityIds: string[];
  encryptedApiKey: string | null;
};

const CONTENT_TYPES: AiContentType[] = [
  "article",
  "recap",
  "headline",
  "social",
  "grade_explanation"
];

const DEFAULT_SETTINGS: StoredAiSettings = {
  baseUrl: "http://localhost:1234/v1",
  model: "",
  contentModels: {},
  promptPackId: "balanced",
  disabledPersonalityIds: [],
  encryptedApiKey: null
};

export function readAiSettings(): AiSettings {
  const stored = readStoredSettings();
  return {
    baseUrl: stored.baseUrl,
    model: stored.model,
    apiKeyConfigured: Boolean(stored.encryptedApiKey),
    contentModels: stored.contentModels,
    promptPackId: stored.promptPackId,
    disabledPersonalityIds: stored.disabledPersonalityIds
  };
}

export function readAiApiKey(): string | null {
  const encrypted = readStoredSettings().encryptedApiKey;
  if (!encrypted) return null;
  if (!safeStorage.isEncryptionAvailable()) {
    throw new Error("Encrypted credential storage is unavailable on this system.");
  }
  try {
    return safeStorage.decryptString(Buffer.from(encrypted, "base64"));
  } catch {
    throw new Error("The stored AI API key could not be decrypted.");
  }
}

export function writeAiSettings(patch: AiSettingsPatch): AiSettings {
  const current = readStoredSettings();
  let encryptedApiKey = current.encryptedApiKey;
  if (patch.apiKey !== undefined) {
    const apiKey = patch.apiKey?.trim() ?? "";
    if (!apiKey) {
      encryptedApiKey = null;
    } else {
      if (!safeStorage.isEncryptionAvailable()) {
        throw new Error("Encrypted credential storage is unavailable on this system.");
      }
      encryptedApiKey = safeStorage.encryptString(apiKey).toString("base64");
    }
  }

  const next: StoredAiSettings = {
    baseUrl:
      patch.baseUrl === undefined ? current.baseUrl : normalizeString(patch.baseUrl),
    model: patch.model === undefined ? current.model : normalizeString(patch.model),
    contentModels:
      patch.contentModels === undefined
        ? current.contentModels
        : normalizeContentModels(patch.contentModels),
    promptPackId:
      patch.promptPackId === undefined
        ? current.promptPackId
        : normalizeString(patch.promptPackId) || DEFAULT_SETTINGS.promptPackId,
    disabledPersonalityIds:
      patch.disabledPersonalityIds === undefined
        ? current.disabledPersonalityIds
        : normalizeStringList(patch.disabledPersonalityIds),
    encryptedApiKey
  };
  const path = getAiSettingsPath();
  mkdirSync(dirname(path), { recursive: true });
  writeFileSync(path, `${JSON.stringify(next, null, 2)}\n`, "utf-8");
  return readAiSettings();
}

function readStoredSettings(): StoredAiSettings {
  const path = getAiSettingsPath();
  if (!existsSync(path)) return { ...DEFAULT_SETTINGS };
  try {
    const parsed = JSON.parse(readFileSync(path, "utf-8")) as Partial<StoredAiSettings>;
    return {
      baseUrl: normalizeString(parsed.baseUrl) || DEFAULT_SETTINGS.baseUrl,
      model: normalizeString(parsed.model),
      contentModels: normalizeContentModels(parsed.contentModels ?? {}),
      promptPackId:
        normalizeString(parsed.promptPackId) || DEFAULT_SETTINGS.promptPackId,
      disabledPersonalityIds: normalizeStringList(
        parsed.disabledPersonalityIds ?? []
      ),
      encryptedApiKey:
        typeof parsed.encryptedApiKey === "string" && parsed.encryptedApiKey
          ? parsed.encryptedApiKey
          : null
    };
  } catch {
    return { ...DEFAULT_SETTINGS };
  }
}

function getAiSettingsPath(): string {
  return join(app.getPath("userData"), "ai-settings.json");
}

function normalizeContentModels(value: AiContentModelMap): AiContentModelMap {
  return Object.fromEntries(
    CONTENT_TYPES.flatMap((contentType) => {
      const model = normalizeString(value[contentType]);
      return model ? [[contentType, model]] : [];
    })
  ) as AiContentModelMap;
}

function normalizeString(value: unknown): string {
  return typeof value === "string" ? value.trim() : "";
}

function normalizeStringList(value: unknown): string[] {
  if (!Array.isArray(value)) return [];
  return Array.from(
    new Set(
      value.flatMap((item) => {
        const normalized = normalizeString(item);
        return normalized ? [normalized] : [];
      })
    )
  ).sort();
}
