const fs = require("fs");
const path = require("path");
const { writeJsonAtomicSync } = require("../../../src/services/safe-files");

class ToolSettings {
  constructor(options = {}) {
    this.installer = options.installer;
  }

  describe(toolId) {
    const local = this.installer.local(toolId);
    if (!local?.valid) throw new Error(local?.error || "Installed AI tool metadata is invalid.");
    const schema = normalizeSchema(local.metadata.settings_schema);
    if (!Object.keys(schema).length) throw new Error("This AI tool does not expose configurable settings.");
    const values = this.readValues(local.dir, schema, local.metadata);
    return {
      tool_id: toolId,
      display_name: local.metadata.display_name,
      schema,
      values: redactSecrets(values, schema),
      configured_secrets: Object.fromEntries(
        Object.entries(schema)
          .filter(([, field]) => field.secret === true)
          .map(([key]) => [key, Boolean(values[key])])
      )
    };
  }

  save(toolId, input) {
    const local = this.installer.local(toolId);
    if (!local?.valid) throw new Error(local?.error || "Installed AI tool metadata is invalid.");
    const schema = normalizeSchema(local.metadata.settings_schema);
    if (!Object.keys(schema).length) throw new Error("This AI tool does not expose configurable settings.");
    const current = this.readValues(local.dir, schema, local.metadata);
    const next = {};
    for (const [key, field] of Object.entries(schema)) {
      const incoming = input?.[key];
      if (field.secret === true && (incoming == null || String(incoming) === "")) {
        next[key] = current[key] ?? field.default;
      } else {
        next[key] = normalizeValue(incoming, field, key);
      }
    }
    const file = settingsFile(local.dir);
    writeJsonAtomicSync(file, next, { mode: 0o600 });
    return this.describe(toolId);
  }

  reset(toolId) {
    const local = this.installer.local(toolId);
    if (!local?.valid) throw new Error(local?.error || "Installed AI tool metadata is invalid.");
    const schema = normalizeSchema(local.metadata.settings_schema);
    const values = Object.fromEntries(
      Object.entries(schema).map(([key, field]) => [key, normalizeValue(field.default, field, key)])
    );
    const file = settingsFile(local.dir);
    writeJsonAtomicSync(file, values, { mode: 0o600 });
    return this.describe(toolId);
  }

  readRaw(toolId) {
    const local = this.installer.local(toolId);
    if (!local?.valid) throw new Error(local?.error || "Installed AI tool metadata is invalid.");
    const schema = normalizeSchema(local.metadata.settings_schema);
    return this.readValues(local.dir, schema, local.metadata);
  }

  readValues(toolDir, schema, metadata = {}) {
    let stored = {};
    try { stored = JSON.parse(fs.readFileSync(settingsFile(toolDir), "utf8")); }
    catch {}
    stored = migrateStoredSettings(toolDir, metadata, stored, schema);
    return Object.fromEntries(
      Object.entries(schema).map(([key, field]) => {
        try {
          return [key, normalizeValue(stored[key] ?? field.default, field, key)];
        } catch {
          return [key, normalizeValue(field.default, field, key)];
        }
      })
    );
  }
}

function migrateStoredSettings(toolDir, metadata, stored, schema) {
  const relative = String(metadata.settings_migrator || "");
  if (!relative) return stored;
  const target = path.resolve(toolDir, relative);
  if (!target.startsWith(`${path.resolve(toolDir)}${path.sep}`) || !fs.existsSync(target)) return stored;
  try {
    const module = require(target);
    if (typeof module.migrateSettings !== "function") return stored;
    const fallback = Object.fromEntries(
      Object.entries(schema).map(([key, field]) => [key, field.default])
    );
    const migrated = module.migrateSettings(stored, fallback);
    return migrated && typeof migrated === "object" && !Array.isArray(migrated) ? migrated : stored;
  } catch {
    return stored;
  }
}

function normalizeSchema(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
  return Object.fromEntries(Object.entries(value).map(([key, field]) => {
    if (!/^[a-z][a-z0-9_]*$/i.test(key) || !field || typeof field !== "object" || Array.isArray(field)) {
      throw new Error("settings_schema contains an invalid field.");
    }
    const type = String(field.type || "string");
    if (!["string", "integer", "number", "boolean", "enum", "string_list", "multi_select"].includes(type)) {
      throw new Error(`Unsupported settings type for ${key}.`);
    }
    const options = Array.isArray(field.options) ? field.options.map(String) : [];
    if (["enum", "multi_select"].includes(type) && !options.length) {
      throw new Error(`${key} must define options.`);
    }
    return [key, {
      type,
      label: String(field.label || key.replaceAll("_", " ")),
      description: String(field.description || ""),
      default: field.default ?? defaultValue(type, options),
      options,
      minimum: finiteOrNull(field.minimum),
      maximum: finiteOrNull(field.maximum),
      secret: field.secret === true,
      required: field.required === true,
      rows: Math.max(2, Math.min(12, Number.parseInt(field.rows, 10) || 3))
    }];
  }));
}

function normalizeValue(value, field, key = "setting") {
  if (field.type === "boolean") return value === true || value === "true" || value === "1" || value === "on";
  if (field.type === "integer" || field.type === "number") {
    const number = field.type === "integer" ? Number.parseInt(value, 10) : Number(value);
    if (!Number.isFinite(number)) throw new Error(`${field.label || key} must be a number.`);
    return clamp(number, field.minimum, field.maximum);
  }
  if (field.type === "string_list") {
    const rows = Array.isArray(value) ? value : String(value || "").split(/\r?\n|,/);
    return [...new Set(rows.map((entry) => String(entry).trim()).filter(Boolean))].slice(0, 200);
  }
  if (field.type === "multi_select") {
    const selected = Array.isArray(value) ? value.map(String) : value == null ? [] : [String(value)];
    return [...new Set(selected)].filter((entry) => field.options.includes(entry));
  }
  const text = String(value ?? "").trim();
  if (field.required && !text) throw new Error(`${field.label || key} is required.`);
  if (field.type === "enum" && !field.options.includes(text)) throw new Error(`${field.label || key} is invalid.`);
  return text;
}

function redactSecrets(values, schema) {
  return Object.fromEntries(Object.entries(values).map(([key, value]) => [
    key,
    schema[key]?.secret === true ? "" : value
  ]));
}

function settingsFile(toolDir) {
  return path.join(toolDir, "data", "settings.json");
}

function defaultValue(type, options) {
  if (type === "boolean") return false;
  if (type === "integer" || type === "number") return 0;
  if (type === "string_list" || type === "multi_select") return [];
  if (type === "enum") return options[0] || "";
  return "";
}

function finiteOrNull(value) {
  if (value == null || value === "") return null;
  const number = Number(value);
  return Number.isFinite(number) ? number : null;
}

function clamp(value, minimum, maximum) {
  return Math.max(minimum ?? value, Math.min(maximum ?? value, value));
}

module.exports = {
  ToolSettings,
  normalizeSchema,
  normalizeValue,
  migrateStoredSettings,
  redactSecrets,
  settingsFile
};
