/**
 * One-time migration: agents flat dict → agents (CLI) + apiProfiles (API).
 *
 * Pre-v0.9.20 `settings.agents` held both CLI agent configs (claude /
 * codex / aider / user-named "CLI profiles" with `base:`) AND HTTP API
 * profiles (`type: 'api'`). Two distinct concepts crammed into one
 * dict with field overlap → the new-install pipeline-uses-API-model
 * bug (see todo_pipeline_model_resolution memory).
 *
 * Migration walks settings.agents once on startup:
 *   - `type: 'api'` entries → moved to settings.apiProfiles
 *   - CLI entries: best-effort flatten `base` / `cliType` → `tool`
 *     (if neither can be inferred, entry stays put; UI will skip it
 *     and the user re-adds manually)
 *   - defaultAgent / chatAgent get validated against the new dicts and
 *     downgraded to safe defaults if they point to the wrong kind
 *
 * Before writing anything we drop a backup at
 * `<dataDir>/settings.backup.agents-<ts>.yaml` containing just the
 * agent-related slices of settings.yaml, so a botched migration can be
 * rolled back by hand.
 *
 * Idempotent: once the deprecated fields are gone, the next call is a
 * no-op (returns false, no save).
 */

import { copyFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { getDataDir } from '../dirs';
import type { Settings } from '../settings';

const BUILTIN_TOOLS = new Set(['claude', 'codex', 'aider', 'opencode']);

/** Returns true if anything was mutated (caller should save). */
export function migrateAgentsFlatten(settings: Settings): boolean {
  const needsMigration = detectsLegacyShape(settings);
  if (!needsMigration) return false;

  writeBackup(settings);

  let mutated = false;
  (settings as any).apiProfiles ??= {};

  for (const [id, raw] of Object.entries(settings.agents ?? {}) as [string, any][]) {
    // === 必须 — type='api' 搬到 apiProfiles ===
    if (raw.type === 'api') {
      (settings as any).apiProfiles[id] = {
        name: raw.name ?? id,
        enabled: raw.enabled !== false,
        provider: mapProvider(raw.provider),
        model: raw.model ?? '',
        apiKey: raw.apiKey ?? '',
        baseUrl: raw.baseUrl ?? '',
      };
      delete settings.agents[id];
      mutated = true;
      continue;
    }

    // === Best-effort — 推断 tool 字段 ===
    if (!raw.tool) {
      if (raw.base && BUILTIN_TOOLS.has(raw.base)) raw.tool = raw.base;
      else if (raw.cliType === 'claude-code') raw.tool = 'claude';
      else if (raw.cliType && BUILTIN_TOOLS.has(raw.cliType)) raw.tool = raw.cliType;
      else if (BUILTIN_TOOLS.has(id)) raw.tool = id;
      if (raw.tool) mutated = true;
      // 推断不出 → 留原样,listAgents 会跳过,用户接受手动重加
    }

    // 清掉已过期的字段(只有迁移产生过 tool 才删 base/cliType,避免无 tool 的孤儿丢标识)
    if (raw.tool) {
      if (raw.base !== undefined) { delete raw.base; mutated = true; }
      if (raw.cliType !== undefined) { delete raw.cliType; mutated = true; }
    }
    if (raw.type !== undefined) { delete raw.type; mutated = true; }

    // 顶层 model 字段已被 per-use models 子表完整取代(terminal / task /
    // telegram / help / mobile 各自可配)。0.10 之前的迁移逻辑反方向复制了
    // models.task → model,导致 resolveTerminalLaunch 错把"task 模型"
    // 当成"全场景兜底",新建项目终端被强插 --model。
    //
    // 这里反向收尾:把残留的 model 搬回 models.task(只在 task 为空或
    // 'default' 时填,避免覆盖用户更近期的选择),然后彻底删字段。
    if (raw.model !== undefined) {
      if (!raw.models) raw.models = {};
      if ((!raw.models.task || raw.models.task === 'default') && raw.model !== 'default') {
        raw.models.task = raw.model;
      }
      delete raw.model;
      mutated = true;
    }

    // ── interactiveCmd 字段清理 ──────────────────────────
    // 终端启动统一走 resolveTerminalLaunch(cliCmd = agentCfg.path),
    // interactiveCmd 从未被后端读取,已从 UI / 类型移除。清掉残留死数据。
    if (raw.interactiveCmd !== undefined) {
      delete raw.interactiveCmd;
      mutated = true;
    }
  }

  // === 兜底校验 defaultAgent / chatAgent ===
  if (settings.defaultAgent && !settings.agents?.[settings.defaultAgent]) {
    const wasApi = (settings as any).apiProfiles[settings.defaultAgent];
    console.warn(
      `[migrate-agents] defaultAgent="${settings.defaultAgent}" ` +
      (wasApi ? 'is an API profile, not allowed for tasks. ' : 'not found. ') +
      `Falling back to 'claude'.`
    );
    settings.defaultAgent = 'claude';
    mutated = true;
  }
  if (settings.chatAgent && !(settings as any).apiProfiles[settings.chatAgent]) {
    const wasCli = settings.agents?.[settings.chatAgent];
    console.warn(
      `[migrate-agents] chatAgent="${settings.chatAgent}" ` +
      (wasCli ? 'is a CLI agent, not allowed for chat. ' : 'not found. ') +
      `Falling back.`
    );
    const firstApi = Object.entries((settings as any).apiProfiles)
      .find(([, p]: any) => p.enabled)?.[0];
    settings.chatAgent = firstApi ?? '';
    mutated = true;
  }

  if (mutated) {
    console.log('[migrate-agents] migrated settings.agents → agents + apiProfiles');
  }
  return mutated;
}

function detectsLegacyShape(settings: Settings): boolean {
  for (const cfg of Object.values(settings.agents ?? {}) as any[]) {
    if (cfg.type === 'api') return true;
    if (cfg.base !== undefined) return true;
    if (cfg.cliType !== undefined) return true;
    if (cfg.model !== undefined) return true;     // 顶层 model 已废,改 models.task
    // 没有 tool + 是 builtin id → 也算需要迁移(补 tool 字段)
  }
  // 内置 agent 没有 tool 字段也需要补
  for (const [id, cfg] of Object.entries(settings.agents ?? {}) as [string, any][]) {
    if (!cfg.tool && BUILTIN_TOOLS.has(id)) return true;
  }
  // defaultAgent 指向不存在的 entry 也需要兜底
  if (settings.defaultAgent && !settings.agents?.[settings.defaultAgent]) {
    return true;
  }
  return false;
}

function writeBackup(_settings: Settings): void {
  // Raw byte copy of settings.yaml — never re-serialize the decrypted
  // in-memory form. v0.10.x shipped a YAML.stringify(settings) backup
  // that leaked plaintext API tokens + ANTHROPIC_AUTH_TOKEN into the
  // data dir. Fixed in v0.10.4+.
  try {
    const src = join(getDataDir(), 'settings.yaml');
    if (!existsSync(src)) return;
    const ts = Date.now();
    const backupPath = join(getDataDir(), `settings.backup.agents-${ts}.yaml`);
    copyFileSync(src, backupPath);
    console.log(`[migrate-agents] backup → ${backupPath}`);
  } catch (err) {
    console.warn('[migrate-agents] backup write failed (continuing anyway):', err instanceof Error ? err.message : err);
  }
}

function mapProvider(p?: string): 'anthropic' | 'openai-compatible' {
  if (!p) return 'openai-compatible';
  return /anthropic/i.test(p) ? 'anthropic' : 'openai-compatible';
}
