import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { resolve, join } from 'node:path';
import { homedir } from 'node:os';

export interface MarkusConfig {
  org: {
    id: string;
    name: string;
  };
  llm: {
    defaultProvider: string;
    defaultModel: string;
    providers: Record<string, {
      apiKey?: string;
      baseUrl?: string;
      model?: string;
      enabled?: boolean;
    }>;
    /** Custom model definitions added via UI/API, keyed by provider name */
    customModels?: Record<string, Array<{
      id: string;
      name: string;
      provider: string;
      contextWindow: number;
      maxOutputTokens: number;
      cost: { input: number; output: number; cacheRead?: number; cacheWrite?: number };
      reasoning?: boolean;
      inputTypes?: Array<'text' | 'image'>;
    }>>;
    /** Request timeout in ms for all LLM providers (default: 90s) */
    timeoutMs?: number;
    /** Allow automatic fallback to other providers/models when the primary fails (default: true) */
    autoFallback?: boolean;
  };
  server: {
    apiPort: number;
    webPort: number;
  };
  security?: {
    adminPassword?: string;
    gatewaySecret?: string;
  };
  hub?: {
    url: string;
  };
  mcpServers?: Record<string, {
    command: string;
    args?: string[];
    env?: Record<string, string>;
  }>;
  agent?: {
    /** Safety cap on tool iterations per agent turn (default: 200) */
    maxToolIterations?: number;
    /** Cognitive Preparation Pipeline settings (default: disabled) */
    cognitive?: {
      enabled?: boolean;
      /** Cap depth level: 0=D0, 1=D1, 2=D2, 3=D3 (default: 1 = appraisal only) */
      maxDepth?: number;
      /** Model override for appraisal/reflection calls */
      appraisalModel?: string;
      /** Timeout in ms for CPP LLM calls (default: 15000) */
      timeoutMs?: number;
    };
  };
  browser?: {
    /** Bring Chrome tabs/windows to foreground when agent navigates (default: false) */
    bringToFront?: boolean;
    /** Remote debugging port for persistent Chrome connection (avoids repeated permission dialogs). Set to e.g. 9222 to enable. */
    remoteDebuggingPort?: number;
    /** Automatically close agent-owned tabs when the task completes (default: true) */
    autoCloseTabs?: boolean;
    /** Auto-click Chrome's "Allow debugging" dialog via OS accessibility APIs (macOS/Windows) */
    autoClickAllowDialog?: boolean;
    /** WebSocket port for Chrome extension bridge (default: 9333) */
    extensionBridgePort?: number;
  };
  integrations?: {
    feishu?: { appId?: string; appSecret?: string };
    search?: { serperApiKey?: string; braveApiKey?: string; bochaApiKey?: string };
    embedding?: { apiKey?: string };
  };
  database?: {
    url: string;
  };
  /** File / blob storage for uploads (images, attachments, etc.).
   *  Default: local filesystem at ~/.markus/uploads/ */
  fileStorage?: {
    provider: 'local';
    local?: {
      /** Override the upload directory (default: ~/.markus/uploads/) */
      dir?: string;
    };
    // Future cloud providers:
    // s3?: { bucket: string; region: string; endpoint?: string; accessKeyId?: string; secretAccessKey?: string };
  };
  remote?: {
    enabled?: boolean;
    autoConnect?: boolean;
    hubUrl?: string;
    instanceName?: string;
  };
}

const DEFAULT_CONFIG: MarkusConfig = {
  org: { id: 'default', name: 'My Organization' },
  llm: {
    defaultProvider: 'anthropic',
    defaultModel: 'claude-opus-4-6',
    providers: {},
  },
  server: { apiPort: 8056, webPort: 8057 },
  security: { adminPassword: 'markus123', gatewaySecret: 'markus-gateway-default-secret-change-me' },
  hub: { url: 'https://markus.global' },
};

export function getDefaultConfigPath(): string {
  return join(homedir(), '.markus', 'markus.json');
}

export function loadConfig(configPath?: string): MarkusConfig {
  const p = configPath ?? getDefaultConfigPath();
  if (!existsSync(p)) return DEFAULT_CONFIG;

  const raw = readFileSync(p, 'utf-8');
  const parsed = JSON.parse(raw) as Partial<MarkusConfig>;
  return deepMerge(DEFAULT_CONFIG as unknown as Obj, parsed as unknown as Obj) as unknown as MarkusConfig;
}

/**
 * Merge partial updates into the on-disk markus.json (creates it if absent).
 */
export function saveConfig(updates: Partial<MarkusConfig>, configPath?: string): void {
  const p = configPath ?? getDefaultConfigPath();
  mkdirSync(resolve(p, '..'), { recursive: true });
  let existing: Obj = {};
  if (existsSync(p)) {
    try {
      existing = JSON.parse(readFileSync(p, 'utf-8')) as Obj;
    } catch {
      existing = {};
    }
  }
  const merged = deepMerge(existing, updates as unknown as Obj);
  writeFileSync(p, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
}

type Obj = Record<string, unknown>;

function deepMerge(target: Obj, source: Obj): Obj {
  const result: Obj = { ...target };
  for (const key of Object.keys(source)) {
    const sv = source[key];
    const tv = target[key];
    if (sv && typeof sv === 'object' && !Array.isArray(sv) && tv && typeof tv === 'object' && !Array.isArray(tv)) {
      result[key] = deepMerge(tv as Obj, sv as Obj);
    } else {
      result[key] = sv;
    }
  }
  return result;
}
