const fs = require("fs");
const path = require("path");

const metadata = require("../tool_info.json");
const ORIGINS = new Set(["webui", "discord", "twitch", "youtube", "kick", "other"]);

function defaults() {
  return Object.fromEntries(
    Object.entries(metadata.settings_schema).map(([key, field]) => [key, structuredClone(field.default)])
  );
}

function readSettings(dataDir) {
  const fallback = defaults();
  try {
    const stored = JSON.parse(fs.readFileSync(settingsPath(dataDir), "utf8"));
    return normalizeSettings(migrateSettings(stored, fallback));
  } catch {
    return normalizeSettings(fallback);
  }
}

function ensureSettings(dataDir) {
  if (!fs.existsSync(settingsPath(dataDir))) return writeSettings(dataDir, defaults());
  return readSettings(dataDir);
}

function writeSettings(dataDir, value) {
  const normalized = normalizeSettings(migrateSettings(value, defaults()));
  fs.mkdirSync(dataDir, { recursive: true });
  const file = settingsPath(dataDir);
  const temporary = `${file}.${process.pid}.tmp`;
  fs.writeFileSync(temporary, `${JSON.stringify(normalized, null, 2)}\n`, { mode: 0o600 });
  try { fs.chmodSync(temporary, 0o600); } catch {}
  replaceFile(temporary, file);
  return normalized;
}

function migrateSettings(value = {}, fallback = defaults()) {
  const migrated = { ...fallback, ...value };
  if (!value.provider) migrated.provider = value.provider_endpoint ? "external_json" : "lumi_search_broker";
  if (value.enabled != null && value.enable_search == null) migrated.enable_search = value.enabled === true;
  if (value.safe_search && value.safe_search_level == null) migrated.safe_search_level = value.safe_search;
  if (value.provider_adapter && value.external_provider_adapter == null) {
    migrated.external_provider_adapter = value.provider_adapter;
  }
  if (value.provider_endpoint && value.external_provider_endpoint == null) {
    migrated.external_provider_endpoint = value.provider_endpoint;
  }
  if (value.provider_api_key && value.external_provider_api_key == null) {
    migrated.external_provider_api_key = value.provider_api_key;
  }
  if (value.provider_api_key_header && value.external_provider_api_key_header == null) {
    migrated.external_provider_api_key_header = value.provider_api_key_header;
  }
  if (value.provider_api_key_prefix && value.external_provider_api_key_prefix == null) {
    migrated.external_provider_api_key_prefix = value.provider_api_key_prefix;
  }
  if (value.provider_query_parameter && value.external_provider_query_parameter == null) {
    migrated.external_provider_query_parameter = value.provider_query_parameter;
  }
  if (value.requests_per_minute && value.per_user_per_minute == null) {
    migrated.per_user_per_minute = value.requests_per_minute;
  }
  if (typeof value.show_source_links === "boolean") {
    for (const origin of ["webui", "discord", "twitch"]) {
      const key = `show_source_links_${origin}`;
      if (value[key] == null) migrated[key] = value.show_source_links;
    }
  }
  return migrated;
}

function normalizeSettings(value) {
  return {
    provider: value.provider === "external_json" ? "external_json" : "lumi_search_broker",
    enable_search: value.enable_search !== false,
    enable_fetch_url: value.enable_fetch_url !== false,
    enable_summarize_url: value.enable_summarize_url !== false,
    enable_headless_browser_fallback: value.enable_headless_browser_fallback === true,
    policy_mode: value.policy_mode === "whitelist" ? "whitelist" : "blacklist",
    url_rules: stringList(value.url_rules, 200),
    max_results: integer(value.max_results, 1, 10, 5),
    search_timeout_ms: integer(value.search_timeout_ms, 1000, 30000, 8000),
    fetch_timeout_ms: integer(value.fetch_timeout_ms, 1000, 30000, 10000),
    max_fetch_bytes: integer(value.max_fetch_bytes, 65536, 4194304, 1048576),
    max_extracted_chars: integer(value.max_extracted_chars, 1000, 50000, 12000),
    max_redirects: integer(value.max_redirects, 0, 8, 3),
    cache_ttl_seconds: integer(value.cache_ttl_seconds, 0, 86400, 900),
    safe_search_level: ["off", "moderate", "strict"].includes(value.safe_search_level)
      ? value.safe_search_level
      : "moderate",
    allowed_origins: stringList(value.allowed_origins, 6).filter((origin) => ORIGINS.has(origin)),
    show_source_links_webui: value.show_source_links_webui !== false,
    show_source_links_discord: value.show_source_links_discord !== false,
    show_source_links_twitch: value.show_source_links_twitch === true,
    webui_output_chars: integer(value.webui_output_chars, 300, 12000, 2500),
    discord_output_chars: integer(value.discord_output_chars, 200, 4000, 1200),
    twitch_output_chars: integer(value.twitch_output_chars, 120, 1000, 450),
    youtube_output_chars: integer(value.youtube_output_chars, 120, 3000, 1200),
    kick_output_chars: integer(value.kick_output_chars, 120, 1000, 450),
    other_output_chars: integer(value.other_output_chars, 120, 3000, 700),
    per_user_per_minute: integer(value.per_user_per_minute, 1, 60, 6),
    per_origin_per_minute: integer(value.per_origin_per_minute, 1, 300, 30),
    per_server_per_minute: integer(value.per_server_per_minute, 1, 300, 20),
    external_provider_adapter: value.external_provider_adapter === "generic_json"
      ? "generic_json"
      : "searxng_json",
    external_provider_endpoint: String(value.external_provider_endpoint || "").trim(),
    external_provider_api_key: String(value.external_provider_api_key || "").trim(),
    external_provider_api_key_header: value.external_provider_api_key_header === "Authorization"
      ? "Authorization"
      : "X-API-Key",
    external_provider_api_key_prefix: String(value.external_provider_api_key_prefix || "").trim().slice(0, 32),
    external_provider_query_parameter: /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(
      String(value.external_provider_query_parameter || "")
    ) ? String(value.external_provider_query_parameter) : "q"
  };
}

function capabilityEnabled(settings, capability) {
  return {
    search: settings.enable_search,
    fetch_url: settings.enable_fetch_url,
    summarize_url: settings.enable_summarize_url
  }[capability] === true;
}

function capabilityAvailable(settings, capability) {
  if (!capabilityEnabled(settings, capability)) return false;
  if (capability === "search" &&
      settings.provider === "external_json" &&
      !settings.external_provider_endpoint) return false;
  return true;
}

function sourceLinksEnabled(settings, origin) {
  if (origin === "twitch") return settings.show_source_links_twitch;
  if (origin === "discord") return settings.show_source_links_discord;
  return settings.show_source_links_webui;
}

function settingsPath(dataDir) {
  return path.join(dataDir, "settings.json");
}

function statusPath(dataDir) {
  return path.join(dataDir, "status.json");
}

function readStatus(dataDir) {
  try { return JSON.parse(fs.readFileSync(statusPath(dataDir), "utf8")); }
  catch { return {}; }
}

function writeStatus(dataDir, patch) {
  const next = { ...readStatus(dataDir), ...patch, updated_at: new Date().toISOString() };
  fs.mkdirSync(dataDir, { recursive: true });
  const file = statusPath(dataDir);
  const temporary = `${file}.${process.pid}.tmp`;
  fs.writeFileSync(temporary, `${JSON.stringify(next, null, 2)}\n`);
  replaceFile(temporary, file);
  return next;
}

function replaceFile(source, destination) {
  try { fs.renameSync(source, destination); }
  catch (error) {
    if (!["EEXIST", "EPERM"].includes(error.code)) throw error;
    fs.rmSync(destination, { force: true });
    fs.renameSync(source, destination);
  }
}

function integer(value, minimum, maximum, fallback) {
  const number = Number.parseInt(value, 10);
  return Number.isFinite(number) ? Math.max(minimum, Math.min(maximum, number)) : fallback;
}

function stringList(value, limit) {
  const rows = Array.isArray(value) ? value : String(value || "").split(/\r?\n|,/);
  return [...new Set(rows.map((entry) => String(entry).trim()).filter(Boolean))].slice(0, limit);
}

module.exports = {
  capabilityAvailable,
  capabilityEnabled,
  defaults,
  ensureSettings,
  migrateSettings,
  normalizeSettings,
  readSettings,
  readStatus,
  settingsPath,
  sourceLinksEnabled,
  statusPath,
  writeSettings,
  writeStatus
};
