import { exec } from "./ssh";
import type { AgentKind } from "./agents/types";
import type { DeployMethod } from "./deploy";

// Config file resolution per (kind, user). User suy ra từ deployRef của pm2 ("user:proc"),
// hoặc lấy từ agent.installPath / configJson.configFile (override).

export type ConfigFormat = "json" | "yaml" | "env" | "text";

export type ConfigSpec = {
  candidates: { path: string; format: ConfigFormat }[];
};

export type ResolvedConfig = {
  path: string;
  format: ConfigFormat;
  exists: boolean;
  content: string;
  fields: ExtractedFields;
};

export type ExtractedFields = {
  provider?: string;
  baseURL?: string;
  apiKey?: string;
  model?: string;
  fallbackModel?: string;
  telegramToken?: string;
  webhook?: string;
  webhookToken?: string;
  workspace?: string;
};

// User được suy ra từ pm2 deployRef format "user:proc"
function resolveUser(deployMethod: DeployMethod, deployRef: string): string {
  if (deployMethod === "pm2") {
    const i = deployRef.indexOf(":");
    if (i > 0) return deployRef.slice(0, i);
  }
  return "root";
}

function homeOf(user: string): string {
  return user === "root" ? "/root" : `/home/${user}`;
}

export function defaultCandidates(kind: AgentKind, user: string): ConfigSpec {
  const home = homeOf(user);
  switch (kind) {
    case "opencode":
      return {
        candidates: [
          { path: `${home}/.config/opencode/opencode.json`, format: "json" },
          { path: `${home}/.opencode/config.json`, format: "json" },
        ],
      };
    case "hermes":
      return {
        candidates: [
          { path: `${home}/.hermes/config.yaml`, format: "yaml" },
          { path: `${home}/.hermes/config.yml`, format: "yaml" },
          { path: `${home}/.config/hermes/config.yaml`, format: "yaml" },
        ],
      };
    case "openclaw":
      return {
        candidates: [
          { path: `${home}/.openclaw/openclaw.json`, format: "json" },
          { path: `${home}/.config/openclaw/config.json`, format: "json" },
          { path: `${home}/.openclaw/config.json`, format: "json" },
          { path: `${home}/.npm-global/lib/node_modules/openclaw/config.json`, format: "json" },
          { path: `${home}/.openclaw/.env`, format: "env" },
        ],
      };
  }
}

// Tìm file config đầu tiên tồn tại; nếu user override "configFile" trong agent.configJson thì dùng nó.
export async function resolveConfigPath(args: {
  hostId: string;
  kind: AgentKind;
  deployMethod: DeployMethod;
  deployRef: string;
  configJson: string;
}): Promise<{ path: string; format: ConfigFormat; exists: boolean; tried: string[]; user: string }> {
  const user = resolveUser(args.deployMethod, args.deployRef);

  // Override
  let cfg: { configFile?: string; configFormat?: ConfigFormat } = {};
  try { cfg = JSON.parse(args.configJson || "{}"); } catch { /* keep empty */ }
  const overridePath = cfg.configFile;
  const overrideFmt = (cfg.configFormat as ConfigFormat | undefined) || undefined;

  if (overridePath) {
    const r = await exec(args.hostId, `[ -f ${overridePath} ] && echo YES || echo NO`);
    return { path: overridePath, format: overrideFmt || guessFormat(overridePath), exists: r.stdout.trim() === "YES", tried: [overridePath], user };
  }

  const spec = defaultCandidates(args.kind, user);
  const tried: string[] = [];
  for (const c of spec.candidates) {
    tried.push(c.path);
    const r = await exec(args.hostId, `[ -f ${c.path} ] && echo YES || echo NO`);
    if (r.stdout.trim() === "YES") {
      return { path: c.path, format: c.format, exists: true, tried, user };
    }
  }
  // Trả về candidate đầu tiên kèm exists=false để UI biết tạo mới ở đâu
  return { path: spec.candidates[0].path, format: spec.candidates[0].format, exists: false, tried, user };
}

function guessFormat(path: string): ConfigFormat {
  if (path.endsWith(".json")) return "json";
  if (path.endsWith(".yaml") || path.endsWith(".yml")) return "yaml";
  if (path.endsWith(".env") || /\.env$/.test(path)) return "env";
  return "text";
}

export async function readConfigContent(hostId: string, path: string, user: string): Promise<{ content: string; ok: boolean; error?: string }> {
  // Nếu file thuộc /home/<user>/, dùng sudo -u <user> để né permission.
  const cmd = user === "root" ? `cat ${path} 2>&1` : `sudo -u ${user} cat ${path} 2>&1`;
  const r = await exec(hostId, cmd, { timeoutMs: 10_000 });
  if (!r.ok) return { content: "", ok: false, error: r.stdout || r.stderr };
  return { content: r.stdout, ok: true };
}

export async function writeConfigContent(hostId: string, path: string, content: string, user: string): Promise<{ ok: boolean; error?: string }> {
  // Backup → ghi qua tee (sudo nếu cần).
  const dir = path.replace(/\/[^/]+$/, "");
  const ts = Math.floor(Date.now() / 1000);
  const b64 = Buffer.from(content, "utf-8").toString("base64");
  const script = [
    `mkdir -p ${dir} 2>/dev/null || true`,
    `[ -f ${path} ] && cp ${path} ${path}.bak.${ts} 2>/dev/null || true`,
    `echo ${JSON.stringify(b64)} | base64 -d > ${path}`,
    `chmod 600 ${path}`,
  ].join("; ");
  const cmd = user === "root" ? script : `sudo -u ${user} bash -c ${JSON.stringify(script)}`;
  const r = await exec(hostId, cmd, { timeoutMs: 15_000 });
  if (!r.ok) return { ok: false, error: r.stdout || r.stderr };
  return { ok: true };
}

// ----- Field extraction -----
// Heuristic: chấp nhận khoá viết theo nhiều format (apiKey/api_key, baseURL/base_url, model_fallback…)

export function extractFields(content: string, format: ConfigFormat, kind: AgentKind): ExtractedFields {
  if (!content.trim()) return {};
  if (format === "env") return extractFromEnv(content);
  if (format === "json") {
    try {
      const obj = JSON.parse(content);
      return extractFromObject(obj, kind);
    } catch {
      return {};
    }
  }
  if (format === "yaml") {
    return extractFromYaml(content, kind);
  }
  return {};
}

function extractFromEnv(content: string): ExtractedFields {
  const f: ExtractedFields = {};
  const env: Record<string, string> = {};
  for (const line of content.split("\n")) {
    const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/);
    if (!m) continue;
    let v = m[2].trim();
    if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
    env[m[1]] = v;
  }
  f.apiKey = env.OPENROUTER_API_KEY || env.OPENAI_API_KEY || env.API_KEY || env.ANTHROPIC_API_KEY;
  f.baseURL = env.OPENAI_BASE_URL || env.OPENROUTER_BASE_URL || env.API_BASE_URL || env.BASE_URL;
  f.model = env.MODEL || env.DEFAULT_MODEL;
  f.fallbackModel = env.FALLBACK_MODEL || env.MODEL_FALLBACK || env.SMALL_MODEL;
  f.provider = env.PROVIDER || env.LLM_PROVIDER;
  f.telegramToken = env.TELEGRAM_BOT_TOKEN || env.TELEGRAM_TOKEN || env.TG_TOKEN;
  f.webhook = env.WEBHOOK_URL || env.WEBHOOK;
  f.webhookToken = env.WEBHOOK_TOKEN || env.WEBHOOK_SECRET;
  f.workspace = env.WORKSPACE || env.WORKSPACE_DIR || env.WORK_DIR;
  return clean(f);
}

function extractFromObject(obj: unknown, kind: AgentKind): ExtractedFields {
  const f: ExtractedFields = {};
  if (typeof obj !== "object" || !obj) return f;
  const o = obj as Record<string, unknown>;

  // OpenClaw nested schema: models.providers.<name>.{baseUrl,apiKey}, agents.defaults.model.{primary,fallbacks}, channels.telegram.botToken, hooks.path
  if (kind === "openclaw") {
    const models = o.models as Record<string, unknown> | undefined;
    if (models && typeof models === "object") {
      const provs = (models.providers as Record<string, Record<string, unknown>>) || {};
      const provName = Object.keys(provs)[0];
      if (provName) {
        f.provider = provName;
        const p = provs[provName] || {};
        if (typeof p.baseUrl === "string") f.baseURL = p.baseUrl;
        if (typeof p.apiKey === "string") f.apiKey = p.apiKey;
      }
    }
    const agents = o.agents as Record<string, unknown> | undefined;
    const def = agents?.defaults as Record<string, unknown> | undefined;
    const m = def?.model as Record<string, unknown> | undefined;
    if (m) {
      if (typeof m.primary === "string") f.model = m.primary;
      const fb = m.fallbacks as unknown;
      if (Array.isArray(fb) && typeof fb[0] === "string") f.fallbackModel = fb[0];
    }
    const ch = o.channels as Record<string, unknown> | undefined;
    const tg = ch?.telegram as Record<string, unknown> | undefined;
    if (tg && typeof tg.botToken === "string") f.telegramToken = tg.botToken;
    const hooks = o.hooks as Record<string, unknown> | undefined;
    if (hooks && typeof hooks.path === "string") f.webhook = hooks.path;
    if (hooks && typeof hooks.token === "string") f.webhookToken = hooks.token;
    // Workspace path: agents.defaults.workspace
    const def2 = (o.agents as Record<string, unknown> | undefined)?.defaults as Record<string, unknown> | undefined;
    if (def2 && typeof def2.workspace === "string") f.workspace = def2.workspace;
    return clean(f);
  }

  // Provider name (generic)
  if (typeof o.provider === "string") f.provider = o.provider;

  // OpenCode pattern: provider: { <name>: { options: { apiKey, baseURL } } }, model: "..."
  if (typeof o.provider === "object" && o.provider) {
    const provs = o.provider as Record<string, { options?: Record<string, unknown> }>;
    const names = Object.keys(provs);
    if (names.length) {
      f.provider = f.provider || names[0];
      const opt = provs[names[0]]?.options || {};
      if (typeof opt.apiKey === "string") f.apiKey = opt.apiKey;
      if (typeof opt.baseURL === "string") f.baseURL = opt.baseURL;
    }
  }

  if (typeof o.model === "string") f.model = o.model;
  if (typeof o.small_model === "string") f.fallbackModel = o.small_model;
  if (typeof o.fallbackModel === "string") f.fallbackModel = o.fallbackModel;
  if (typeof o.fallback_model === "string") f.fallbackModel = o.fallback_model;

  // Hermes-style: models: { <provider>: { api_key, base_url, model } }
  if (kind === "hermes" && typeof o.models === "object" && o.models) {
    const provName = (typeof o.provider === "string" ? o.provider : null) || Object.keys(o.models as object)[0];
    if (provName) {
      f.provider = provName;
      const cfg = (o.models as Record<string, Record<string, unknown>>)[provName] || {};
      if (typeof cfg.api_key === "string") f.apiKey = cfg.api_key;
      if (typeof cfg.base_url === "string") f.baseURL = cfg.base_url;
      if (typeof cfg.model === "string") f.model = cfg.model;
      if (typeof cfg.fallback_model === "string") f.fallbackModel = cfg.fallback_model;
    }
  }

  // Telegram + webhook (nhiều shape)
  const tg2 = pick(o, ["telegram", "gateway.telegram", "messaging.telegram"]) as Record<string, unknown> | undefined;
  if (tg2) {
    f.telegramToken = (tg2.token || tg2.bot_token || tg2.botToken || tg2.api_token) as string | undefined;
  }
  if (typeof o.telegram_token === "string") f.telegramToken = o.telegram_token;
  if (typeof o.telegramToken === "string") f.telegramToken = o.telegramToken;

  const wh = pick(o, ["webhook", "webhook.url", "gateway.webhook.url"]) as Record<string, unknown> | string | undefined;
  if (typeof wh === "string") f.webhook = wh;
  else if (wh && typeof wh === "object" && typeof (wh as Record<string, unknown>).url === "string") f.webhook = (wh as Record<string, string>).url;
  if (typeof o.webhook_url === "string") f.webhook = o.webhook_url;

  return clean(f);
}

function pick(obj: Record<string, unknown>, paths: string[]): unknown {
  for (const p of paths) {
    const parts = p.split(".");
    let cur: unknown = obj;
    for (const part of parts) {
      if (cur && typeof cur === "object" && part in (cur as Record<string, unknown>)) {
        cur = (cur as Record<string, unknown>)[part];
      } else {
        cur = undefined; break;
      }
    }
    if (cur !== undefined) return cur;
  }
  return undefined;
}

// Naïve YAML parser (chỉ đủ để extract khoá đơn giản key: value, indented).
function extractFromYaml(content: string, kind: AgentKind): ExtractedFields {
  // Convert yaml → simple object qua heuristic regex (không dùng thư viện ngoài).
  // Ưu tiên các khoá flat thường gặp.
  const f: ExtractedFields = {};
  const get = (rx: RegExp): string | undefined => {
    const m = content.match(rx);
    if (!m) return undefined;
    let v = m[1].trim();
    if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
    return v || undefined;
  };
  f.provider = get(/^\s*provider:\s*(.+)$/m);
  f.model = get(/^\s*model:\s*(.+)$/m);
  f.fallbackModel = get(/^\s*fallback_model:\s*(.+)$/m) || get(/^\s*small_model:\s*(.+)$/m);
  f.apiKey = get(/^\s*api_key:\s*(.+)$/m);
  f.baseURL = get(/^\s*base_url:\s*(.+)$/m);
  f.telegramToken = get(/^\s*(?:bot_)?token:\s*(.+)$/m);
  f.webhook = get(/^\s*webhook(?:_url)?:\s*(.+)$/m) || get(/^\s*url:\s*(.+)$/m);
  // kind tham số dùng cho heuristic mở rộng nếu cần — hiện chưa khác biệt.
  void kind;
  return clean(f);
}

function clean<T extends Record<string, unknown>>(o: T): T {
  for (const k of Object.keys(o)) {
    const v = (o as Record<string, unknown>)[k];
    if (v === undefined || v === null || v === "") delete (o as Record<string, unknown>)[k];
  }
  return o;
}

// ----- Apply fields back vào content (preserve format) -----

export function applyFields(content: string, format: ConfigFormat, kind: AgentKind, patch: ExtractedFields): { content: string; warnings: string[] } {
  const warnings: string[] = [];
  if (format === "env") return { content: applyEnv(content, patch), warnings };
  if (format === "json") {
    try {
      const obj = JSON.parse(content || "{}") as Record<string, unknown>;
      applyJson(obj, kind, patch);
      return { content: JSON.stringify(obj, null, 2) + "\n", warnings };
    } catch {
      warnings.push("JSON không hợp lệ — chỉ ghi raw, không patch field.");
      return { content, warnings };
    }
  }
  if (format === "yaml") {
    return { content: applyYaml(content, patch, warnings), warnings };
  }
  warnings.push(`Format ${format} chưa hỗ trợ patch field — chỉ ghi raw.`);
  return { content, warnings };
}

function applyEnv(content: string, patch: ExtractedFields): string {
  const lines = content.split("\n");
  const map: Record<string, string | undefined> = {
    OPENROUTER_API_KEY: patch.apiKey,
    OPENAI_BASE_URL: patch.baseURL,
    MODEL: patch.model,
    FALLBACK_MODEL: patch.fallbackModel,
    PROVIDER: patch.provider,
    TELEGRAM_BOT_TOKEN: patch.telegramToken,
    WEBHOOK_URL: patch.webhook,
    WEBHOOK_TOKEN: patch.webhookToken,
    WORKSPACE: patch.workspace,
  };
  for (const [k, v] of Object.entries(map)) {
    if (v === undefined) continue;
    const idx = lines.findIndex((l) => new RegExp(`^\\s*${k}\\s*=`).test(l));
    const line = `${k}=${needsQuote(v) ? JSON.stringify(v) : v}`;
    if (idx >= 0) lines[idx] = line;
    else lines.push(line);
  }
  return lines.join("\n");
}
function needsQuote(v: string): boolean {
  return /[#"'\s]/.test(v);
}

function applyJson(obj: Record<string, unknown>, kind: AgentKind, p: ExtractedFields) {
  if (kind === "openclaw") {
    // Apply vào nested schema: models.providers.<name>, agents.defaults.model, channels.telegram, hooks.path
    if (p.provider || p.apiKey || p.baseURL) {
      const models = (obj.models = (obj.models as Record<string, unknown>) || {}) as Record<string, unknown>;
      const provs = (models.providers = (models.providers as Record<string, Record<string, unknown>>) || {}) as Record<string, Record<string, unknown>>;

      // Tìm provider hiện có; nếu p.provider trỏ provider mới → tạo, copy models[] từ provider cũ nếu có
      const existing = Object.keys(provs);
      const targetName = p.provider || existing[0];
      if (targetName) {
        if (!provs[targetName] && existing[0]) {
          // Rename provider cũ
          provs[targetName] = provs[existing[0]];
          delete provs[existing[0]];
        }
        provs[targetName] = provs[targetName] || {};
        if (p.baseURL !== undefined) provs[targetName].baseUrl = p.baseURL;
        if (p.apiKey !== undefined) provs[targetName].apiKey = p.apiKey;
      }
    }
    if (p.model || p.fallbackModel) {
      const agents = (obj.agents = (obj.agents as Record<string, unknown>) || {}) as Record<string, unknown>;
      const def = (agents.defaults = (agents.defaults as Record<string, unknown>) || {}) as Record<string, unknown>;
      const m = (def.model = (def.model as Record<string, unknown>) || {}) as Record<string, unknown>;
      if (p.model !== undefined) m.primary = p.model;
      if (p.fallbackModel !== undefined) {
        const cur = Array.isArray(m.fallbacks) ? (m.fallbacks as string[]) : [];
        m.fallbacks = [p.fallbackModel, ...cur.filter((x) => x !== p.fallbackModel)];
      }
    }
    if (p.telegramToken !== undefined) {
      const ch = (obj.channels = (obj.channels as Record<string, unknown>) || {}) as Record<string, unknown>;
      const tg = (ch.telegram = (ch.telegram as Record<string, unknown>) || { enabled: true }) as Record<string, unknown>;
      tg.botToken = p.telegramToken;
    }
    if (p.webhook !== undefined) {
      const h = (obj.hooks = (obj.hooks as Record<string, unknown>) || { enabled: true }) as Record<string, unknown>;
      h.path = p.webhook;
    }
    if (p.webhookToken !== undefined) {
      const h = (obj.hooks = (obj.hooks as Record<string, unknown>) || { enabled: true }) as Record<string, unknown>;
      h.token = p.webhookToken;
    }
    if (p.workspace !== undefined) {
      const agents = (obj.agents = (obj.agents as Record<string, unknown>) || {}) as Record<string, unknown>;
      const def = (agents.defaults = (agents.defaults as Record<string, unknown>) || {}) as Record<string, unknown>;
      def.workspace = p.workspace;
    }
    return;
  }

  if (kind === "opencode") {
    if (p.provider) {
      obj.provider = obj.provider || {};
      const provs = obj.provider as Record<string, { options?: Record<string, unknown> }>;
      provs[p.provider] = provs[p.provider] || { options: {} };
      provs[p.provider].options = provs[p.provider].options || {};
      if (p.apiKey !== undefined) provs[p.provider].options!.apiKey = p.apiKey;
      if (p.baseURL !== undefined) provs[p.provider].options!.baseURL = p.baseURL;
    }
    if (p.model) obj.model = p.model;
    if (p.fallbackModel) obj.small_model = p.fallbackModel;
  } else {
    // hermes (json fallback) hoặc generic
    if (p.provider) obj.provider = p.provider;
    if (p.model) obj.model = p.model;
    if (p.fallbackModel) obj.fallbackModel = p.fallbackModel;
    if (p.apiKey) obj.apiKey = p.apiKey;
    if (p.baseURL) obj.baseURL = p.baseURL;
  }
  if (p.telegramToken) {
    const tg = (obj.telegram = (obj.telegram as Record<string, unknown>) || {}) as Record<string, unknown>;
    tg.token = p.telegramToken;
  }
  if (p.webhook) {
    obj.webhook_url = p.webhook;
  }
  if (p.webhookToken) {
    obj.webhook_token = p.webhookToken;
  }
  if (p.workspace) {
    obj.workspace = p.workspace;
  }
}

function applyYaml(content: string, p: ExtractedFields, warnings: string[]): string {
  // Cập nhật in-place với regex; nếu khoá chưa có → append cuối.
  let out = content;
  const setKey = (key: string, val: string | undefined, anchorPattern?: RegExp) => {
    if (val === undefined) return;
    const rx = new RegExp(`^(\\s*${key}:\\s*)(.*)$`, "m");
    if (rx.test(out)) {
      out = out.replace(rx, `$1${yamlValue(val)}`);
    } else if (anchorPattern && anchorPattern.test(out)) {
      out = out.replace(anchorPattern, (m) => `${m}\n${key}: ${yamlValue(val)}`);
    } else {
      if (!out.endsWith("\n")) out += "\n";
      out += `${key}: ${yamlValue(val)}\n`;
    }
  };
  setKey("provider", p.provider);
  setKey("model", p.model);
  setKey("fallback_model", p.fallbackModel);
  setKey("api_key", p.apiKey);
  setKey("base_url", p.baseURL);
  // Telegram + webhook hơi mơ hồ trong yaml — chỉ ghi flat:
  setKey("telegram_token", p.telegramToken);
  setKey("webhook_url", p.webhook);
  setKey("webhook_token", p.webhookToken);
  setKey("workspace", p.workspace);
  warnings.push("YAML patch dùng heuristic flat — kiểm tra lại nếu file có cấu trúc lồng nhau.");
  return out;
}

function yamlValue(v: string): string {
  if (/[:#\n"']/.test(v) || v.includes(" ")) return JSON.stringify(v);
  return v;
}
