import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { join, dirname } from 'node:path';
import YAML from 'yaml';
import { encryptSecret, decryptSecret, isEncrypted, SECRET_FIELDS } from './crypto';
import { getDataDir } from './dirs';

const DATA_DIR = getDataDir();
const SETTINGS_FILE = join(DATA_DIR, 'settings.yaml');

export interface AgentEntry {
  // Base agent fields (for detected agents like claude, codex, aider)
  path?: string; name?: string; enabled?: boolean;
  flags?: string[]; taskFlags?: string; interactiveCmd?: string; resumeFlag?: string; outputFormat?: string;
  models?: { terminal?: string; task?: string; telegram?: string; help?: string; mobile?: string };
  skipPermissionsFlag?: string;
  requiresTTY?: boolean;
  // Profile fields (for profiles that extend a base agent)
  base?: string;                 // base agent ID (e.g., 'claude') — makes this a profile
  // API profile fields
  type?: 'cli' | 'api';         // 'api' = API mode, default = 'cli'
  provider?: string;             // API provider label (e.g., 'anthropic', 'openai', 'litellm', 'grok', 'google')
  model?: string;                // model override (for both CLI and API profiles)
  apiKey?: string;               // per-profile API key (encrypted)
  /**
   * Base URL override for API profiles. Use this for LiteLLM / Azure /
   * self-hosted proxies. Empty = default for the provider's protocol.
   * For CLI profiles, set base URL via `env` (ANTHROPIC_BASE_URL etc.).
   */
  baseUrl?: string;
  env?: Record<string, string>;  // environment variables injected when spawning CLI
  cliType?: 'claude-code' | 'codex' | 'aider' | 'generic'; // CLI tool type — determines session support, resume flags, etc.
  profile?: string;              // linked profile ID — overrides model, env, etc. when launching
}

/**
 * External MCP server configuration — merged into per-project .mcp.json
 * alongside the built-in `forge` entry. Format mirrors the MCP standard.
 */
export interface McpServerConfig {
  type: 'stdio' | 'sse' | 'streamable-http';
  // stdio
  command?: string;
  args?: string[];
  env?: Record<string, string>;
  // sse / streamable-http
  url?: string;
  headers?: Record<string, string>;
}

export interface Settings {
  projectRoots: string[];
  docRoots: string[];
  claudePath: string;
  claudeHome: string;
  telegramBotToken: string;
  telegramChatId: string;
  notifyOnComplete: boolean;
  notifyOnFailure: boolean;
  tunnelAutoStart: boolean;
  telegramTunnelPassword: string;
  taskModel: string;
  pipelineModel: string;
  telegramModel: string;
  skipPermissions: boolean;
  manageClaudeConfig: boolean;
  notificationRetentionDays: number;
  skillsRepoUrl: string;
  /**
   * Remote registry for connector manifests. Points to the raw-content
   * URL of a forge-connectors-style repo. Setting empty disables sync
   * (offline only). Default: `aiwatching/forge-connectors`.
   */
  connectorsRepoUrl: string;
  /**
   * Remote registry for workflow templates (recipes + pipelines). Same
   * shape as connectorsRepoUrl. Default: `aiwatching/forge-workflow`.
   */
  workflowRepoUrl: string;
  displayName: string;
  displayEmail: string;
  favoriteProjects: string[];
  defaultAgent: string;
  telegramAgent: string;
  docsAgent: string;
  /** Default API agent profile used by Forge's chat backend (extension / web / CLI / Telegram /chat). */
  chatAgent: string;
  /**
   * Temper memory service — long-term memory backend for the chat agent.
   * When url + key are both set, chat fetches pinned blocks / search hits
   * on every turn and exposes memory_* tools to the LLM.
   */
  temperUrl: string;
  temperKey: string;
  /** Optional namespace override (empty = use the key's default namespace). */
  temperNamespace: string;
  /**
   * Which memory backend the chat agent uses.
   *   'auto'   — Temper if URL+key are set, otherwise local SQLite (default).
   *   'local'  — always local SQLite, even if Temper credentials are filled.
   *   'temper' — always Temper; if credentials are missing, memory is disabled.
   */
  memoryBackend: 'auto' | 'local' | 'temper';
  agents: Record<string, AgentEntry>;
  /** Extra MCP servers merged into each project's .mcp.json (chrome-devtools-mcp etc.) */
  mcpServers: Record<string, McpServerConfig>;
  /**
   * Preferred timezone for rendering timestamps in the UI. Format: IANA
   * (e.g. "America/Los_Angeles", "Asia/Shanghai", "Europe/Berlin").
   * Empty = use the OS / browser default (Intl.DateTimeFormat().resolvedOptions().timeZone).
   * Used by the extension's date formatter for chat / jobs / pipeline run timestamps.
   */
  timezone: string;
}

const defaults: Settings = {
  projectRoots: [],
  docRoots: [],
  claudePath: '',
  claudeHome: '',
  telegramBotToken: '',
  telegramChatId: '',
  notifyOnComplete: true,
  notifyOnFailure: true,
  tunnelAutoStart: false,
  telegramTunnelPassword: '',
  taskModel: 'default',
  pipelineModel: 'default',
  telegramModel: 'sonnet',
  skipPermissions: false,
  manageClaudeConfig: true,
  notificationRetentionDays: 30,
  skillsRepoUrl: 'https://raw.githubusercontent.com/aiwatching/forge-skills/main',
  connectorsRepoUrl: 'https://raw.githubusercontent.com/aiwatching/forge-connectors/main',
  workflowRepoUrl: 'https://raw.githubusercontent.com/aiwatching/forge-workflow/main',
  displayName: 'Forge',
  displayEmail: '',
  favoriteProjects: [],
  defaultAgent: 'claude',
  telegramAgent: '',
  docsAgent: '',
  chatAgent: '',
  temperUrl: '',
  temperKey: '',
  temperNamespace: '',
  memoryBackend: 'auto',
  agents: {},
  mcpServers: {},
  timezone: '',
};

/** Decrypt nested apiKey fields in agent profiles */
function decryptNestedSecrets(settings: Settings): void {
  if (settings.agents) {
    for (const [id, a] of Object.entries(settings.agents)) {
      if (a.apiKey && isEncrypted(a.apiKey)) {
        a.apiKey = decryptSecret(a.apiKey);
      }
      // Defensive: a past bug let the masked placeholder get encrypted
      // and written back. Clear it so callers see "no key" instead of
      // trying to send '••••••••' as a Bearer header.
      if (a.apiKey && /^•+$/.test(a.apiKey)) {
        console.warn(`[settings] agent '${id}' has a placeholder apiKey — clearing. Re-enter it via Settings.`);
        a.apiKey = '';
      }
    }
  }
}

/** Encrypt nested apiKey fields in agent profiles */
function encryptNestedSecrets(settings: Settings): void {
  if (settings.agents) {
    for (const a of Object.values(settings.agents)) {
      if (a.apiKey && !isEncrypted(a.apiKey)) {
        a.apiKey = encryptSecret(a.apiKey);
      }
    }
  }
}

/** Load settings with secrets decrypted (for internal use) */
export function loadSettings(): Settings {
  if (!existsSync(SETTINGS_FILE)) return { ...defaults };
  try {
    const raw = readFileSync(SETTINGS_FILE, 'utf-8');
    const parsed = { ...defaults, ...YAML.parse(raw) };
    // Decrypt top-level secret fields
    for (const field of SECRET_FIELDS) {
      if (parsed[field] && isEncrypted(parsed[field])) {
        parsed[field] = decryptSecret(parsed[field]);
      }
    }
    // Decrypt nested apiKeys
    decryptNestedSecrets(parsed);
    return parsed;
  } catch {
    return { ...defaults };
  }
}

/** Load settings with secrets masked (for API response to frontend) */
export function loadSettingsMasked(): Settings & { _secretStatus: Record<string, boolean> } {
  const settings = loadSettings();
  const status: Record<string, boolean> = {};
  for (const field of SECRET_FIELDS) {
    status[field] = !!settings[field];
    settings[field] = settings[field] ? '••••••••' : '';
  }
  // Mask nested apiKeys (agent profiles only)
  if (settings.agents) {
    for (const [name, a] of Object.entries(settings.agents)) {
      if (a.apiKey) {
        status[`agents.${name}.apiKey`] = true;
        a.apiKey = '••••••••';
      }
    }
  }
  return { ...settings, _secretStatus: status };
}

/** Save settings, encrypting secret fields */
export function saveSettings(settings: Settings) {
  const dir = dirname(SETTINGS_FILE);
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
  // Deep copy to avoid mutating original
  const toSave = JSON.parse(JSON.stringify(settings));
  // Encrypt top-level secret fields
  for (const field of SECRET_FIELDS) {
    if (toSave[field] && !isEncrypted(toSave[field])) {
      toSave[field] = encryptSecret(toSave[field]);
    }
  }
  // Encrypt nested apiKeys
  encryptNestedSecrets(toSave);
  writeFileSync(SETTINGS_FILE, YAML.stringify(toSave), 'utf-8');
}

/** Verify a secret field's current value */
export function verifySecret(field: string, value: string): boolean {
  const settings = loadSettings();
  const current = (settings as any)[field] || '';
  return current === value;
}
