/**
 * CLI 設定檔管理 — 三層分層解析（SDD: sdk-and-website/config-layering.md）
 *   優先序：env 變數 > 專案層 .arcrun.yaml（就近往上找）> 全域 ~/.arcrun/config.yaml
 *   解壓測 #7（AI/CI 非互動）+ #8（接案多帳號），仿 git config / Claude Code MCP 模式。
 */
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { homedir } from 'node:os';
import { join, dirname, parse as parsePath } from 'node:path';
import yaml from 'js-yaml';

export interface ArcrunConfig {
  mode: 'local' | 'standard' | 'self-hosted';
  // Standard 模式
  api_key?: string;           // arcrun.dev API Key（ak_前綴）
  // Self-hosted 模式
  cloudflare_account_id?: string;
  user_kv_namespace_id?: string;
  cf_api_token?: string;
  cypher_executor_url?: string;
  credentials_kv_namespace_id?: string;
  webhooks_kv_namespace_id?: string;
  // 共用
  // MCP server URL（薄殼原則：CLI 與 MCP 同一份身份解析）。
  //   self-hosted / 接案：指向自己 / 客戶的 remote MCP Worker（綁該帳號的 cypher）。
  //   未設 → fallback 平台預設（SaaS 用戶）。acr mcp-setup 依此寫專案 .mcp.json，
  //   讓「進哪個專案資料夾 → Claude Code 連那台 MCP」自動生效。
  //   SDD: sdk-and-website/mcp-account-source.md
  mcp_url?: string;
  multi_tenant?: boolean;
  // 語義查詢開關（issue #7 / SDD T2.4，self-hosted 從零做）。
  //   🔴 2026-08-09 預設翻轉（leo：「語義搜尋已經確定是一安裝就提供的功能」）：
  //   未設 → **視同開**（init/update 皆以 `!== false` 判斷）。只有顯式 false 才關。
  //   true/未設 → deploy 時建 CF Vectorize index 並注入 kbdb worker 的 [[vectorize]]+[ai] binding；
  //          kbdb embed 模組啟用（寫入時對標記 embed 的 entry embed、search 支援 mode=semantic）。
  //   false → base 維持 LIKE keyword（顯式選擇才有這個狀態；缺欄位不再等於關——
  //          舊語意會讓 acr update 把正常實例的 binding 靜默剝掉，畫面再謊稱「沒開通」）。
  kbdb_embed?: boolean;
  // 暴露 consent 閘已移除（leo 2026-06-29，Arcrun#13）。此欄位保留只為向後相容舊 config.yaml
  // （讀到不報錯，不再寫入/檢查）。
  exposure_consented?: Record<string, { confirmed_at: string; suppress_future?: boolean }>;
}

const CONFIG_DIR = join(homedir(), '.arcrun');
const CONFIG_PATH = join(CONFIG_DIR, 'config.yaml');

/** 專案層設定檔名（就近往上找）。含憑證 → 必須 gitignore（見 createCredentialsYamlIfMissing）。*/
export const PROJECT_CONFIG_NAME = '.arcrun.yaml';

/** 設定來源層級（acr config --where 用，讓使用者知道每個值來自哪一層，避免用錯帳號）。*/
export type ConfigSource = 'env' | 'project' | 'global' | 'default';

/** env 變數 → config 欄位映射（最高層覆蓋）。CF 兩個沿用 wrangler 慣用名，CI 設一次兩邊通用。*/
const ENV_MAP: Record<string, keyof ArcrunConfig> = {
  ARCRUN_MODE: 'mode',
  // NAMESPACE / ARCRUN_NAMESPACE：self-hosted 單租戶的資料分區標籤（明碼，用戶自填）。
  //   沿用 api_key 欄位 + 路徑（KV key 前綴 {api_key}:cred:{name}），故 self-hosted 無需平台發 api_key。
  //   這是「分區標籤」非「認證密碼」：你的 cypher 在你自己的 CF，無「別人」會冒用；
  //   要防外部呼叫請對 webhook 加保護（mindset §6）。SaaS 仍走 register 發的真 api_key（同一條路徑，不分叉）。
  NAMESPACE: 'api_key',
  ARCRUN_NAMESPACE: 'api_key',
  ARCRUN_API_KEY: 'api_key',
  ARCRUN_CYPHER_EXECUTOR_URL: 'cypher_executor_url',
  ARCRUN_MCP_URL: 'mcp_url',
  CLOUDFLARE_ACCOUNT_ID: 'cloudflare_account_id',
  CLOUDFLARE_API_TOKEN: 'cf_api_token',
};

/**
 * 平台預設 MCP URL（mcp_url 未設時的 fallback，SaaS 用戶用）。
 * MCP 搬進 arcrun 主庫後改用 arcrun.dev zone（mcp/wrangler.toml route = mcp.arcrun.dev）。
 */
// MCP streamable-http 端點是 /mcp（根路徑 404）。少了 /mcp → client 連線 Failed。
export const DEFAULT_MCP_URL = 'https://mcp.arcrun.dev/mcp';

/**
 * 公庫 URL（recipe pull/search/submit-p 的對象，kbdb-base §7.5）。
 * 公庫 = 官方 SaaS cypher（唯一公共真相）。self-hosted 用戶的「私庫」是自己的 cypher
 * （getCypherExecutorUrl），但 pull/搜尋/投稿都對著**官方公庫**這個固定 URL。
 * fork 者可用 ARCRUN_PUBLIC_LIBRARY_URL env 覆蓋。
 */
export const DEFAULT_PUBLIC_LIBRARY_URL =
  process.env.ARCRUN_PUBLIC_LIBRARY_URL ?? 'https://cypher.arcrun.dev';

export function configExists(): boolean {
  return existsSync(CONFIG_PATH) || findProjectConfig() !== undefined;
}

/** 從 startDir 就近往上逐層找專案層 .arcrun.yaml，回傳第一個命中的路徑（停在檔案系統根）。*/
export function findProjectConfig(startDir: string = process.cwd()): string | undefined {
  let dir = startDir;
  const root = parsePath(dir).root;
  // 防呆上界：層數不會無限（root 一定到得了），但仍加保險避免異常路徑死迴圈。
  for (let i = 0; i < 256; i++) {
    const candidate = join(dir, PROJECT_CONFIG_NAME);
    if (existsSync(candidate)) return candidate;
    if (dir === root) break;
    const parent = dirname(dir);
    if (parent === dir) break;
    dir = parent;
  }
  return undefined;
}

/** 讀全域設定（不分層）。無檔回 undefined。*/
function readGlobalConfig(): Partial<ArcrunConfig> | undefined {
  if (!existsSync(CONFIG_PATH)) return undefined;
  return (yaml.load(readFileSync(CONFIG_PATH, 'utf8')) as Partial<ArcrunConfig>) ?? undefined;
}

/** 讀專案層設定（不分層）。無檔回 undefined。*/
function readProjectConfig(): Partial<ArcrunConfig> | undefined {
  const path = findProjectConfig();
  if (!path) return undefined;
  return (yaml.load(readFileSync(path, 'utf8')) as Partial<ArcrunConfig>) ?? undefined;
}

/**
 * 載入 .env（就近往上找，同 .arcrun.yaml）到 process.env，讓用戶照 Node/Python 慣例
 * 在 .env 設 NAMESPACE / CLOUDFLARE_* 等即生效。不覆蓋「已存在於 shell」的 env（shell > .env）。
 * 自己解析（不引入 dotenv 依賴）。只認單純 KEY=VALUE，忽略空行/註解/引號。
 */
let _envFileLoaded = false;
function loadDotEnvOnce(): void {
  if (_envFileLoaded) return;
  _envFileLoaded = true;
  // 從 cwd 就近往上找 .env（停在含 .arcrun.yaml 的專案根或檔案系統根）
  let dir = process.cwd();
  const root = parsePath(dir).root;
  for (let i = 0; i < 256; i++) {
    const candidate = join(dir, '.env');
    if (existsSync(candidate)) {
      try {
        for (const rawLine of readFileSync(candidate, 'utf8').split('\n')) {
          const line = rawLine.trim();
          if (!line || line.startsWith('#')) continue;
          const eq = line.indexOf('=');
          if (eq < 1) continue;
          const k = line.slice(0, eq).trim();
          let v = line.slice(eq + 1).trim();
          if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
            v = v.slice(1, -1);
          }
          // shell 已設的優先（不覆蓋），符合「env > .env」直覺
          if (process.env[k] === undefined) process.env[k] = v;
        }
      } catch { /* .env 讀不到不致命 */ }
      break;
    }
    if (dir === root) break;
    const parent = dirname(dir);
    if (parent === dir) break;
    dir = parent;
  }
}

/** 蒐集 env 覆蓋（只取有設值的 env，欄位級）。*/
function readEnvOverrides(): Partial<ArcrunConfig> {
  loadDotEnvOnce();
  const out: Partial<ArcrunConfig> = {};
  for (const [envName, field] of Object.entries(ENV_MAP)) {
    const v = process.env[envName];
    if (v !== undefined && v !== '') {
      // mode 需窄型別；其餘皆 string 欄位。
      (out as Record<string, unknown>)[field] = v;
    }
  }
  // bool 開關（issue #7）：env 可選覆蓋，'true'/'1' → true。
  const embedEnv = process.env.ARCRUN_KBDB_EMBED;
  if (embedEnv !== undefined && embedEnv !== '') {
    out.kbdb_embed = embedEnv === 'true' || embedEnv === '1';
  }
  return out;
}

/**
 * 三層分層解析：全域 → 疊專案層 → 疊 env（欄位級 merge，高層只覆蓋它提供的欄位）。
 * 任一層都沒有 mode 時 fallback 'local'，讓 validate --offline 等在無設定下可運作。
 */
export function loadConfig(): ArcrunConfig {
  const merged: Partial<ArcrunConfig> = {
    ...(readGlobalConfig() ?? {}),
    ...(readProjectConfig() ?? {}),
    ...readEnvOverrides(),
  };
  if (!merged.mode) merged.mode = 'local';
  return merged as ArcrunConfig;
}

/** 解析每個關鍵欄位的最終值與來源層（acr config --where 用）。*/
export function resolveConfigSources(): Array<{ field: keyof ArcrunConfig; value: string; source: ConfigSource }> {
  const global = readGlobalConfig() ?? {};
  const project = readProjectConfig() ?? {};
  const env = readEnvOverrides();
  const fields: (keyof ArcrunConfig)[] = [
    'mode', 'api_key', 'cloudflare_account_id',
    'cf_api_token', 'cypher_executor_url', 'mcp_url',
  ];
  const rows: Array<{ field: keyof ArcrunConfig; value: string; source: ConfigSource }> = [];
  for (const f of fields) {
    let value: unknown;
    let source: ConfigSource = 'default';
    if (f in env) { value = env[f]; source = 'env'; }
    else if (f in project) { value = project[f]; source = 'project'; }
    else if (f in global) { value = global[f]; source = 'global'; }
    else if (f === 'mode') { value = 'local'; source = 'default'; }
    else continue;
    rows.push({ field: f, value: String(value), source });
  }
  return rows;
}

/** 回傳本次解析實際採用的專案層設定檔路徑（無則 undefined）。acr config --where 顯示用。*/
export function activeProjectConfigPath(): string | undefined {
  return findProjectConfig();
}

export function saveConfig(config: ArcrunConfig): void {
  mkdirSync(CONFIG_DIR, { recursive: true });
  writeFileSync(CONFIG_PATH, yaml.dump(config), 'utf8');
}

export function getCypherExecutorUrl(config: ArcrunConfig): string {
  if (config.mode === 'self-hosted' && config.cypher_executor_url) {
    return config.cypher_executor_url;
  }
  return 'https://cypher.arcrun.dev';
}

/**
 * 取得 MCP server URL（薄殼原則：與 cypher_url 同一份 config 解析）。
 *   config 有 mcp_url（env/專案/全域 任一層）→ 用它；否則 fallback 平台預設。
 *   acr mcp-setup 用此決定要寫進專案 .mcp.json 的 URL → 切資料夾自動切 MCP。
 */
export function getMcpUrl(config: ArcrunConfig): string {
  return config.mcp_url && config.mcp_url.trim() !== '' ? config.mcp_url : DEFAULT_MCP_URL;
}
