import type { GatewayRequest, OutboundMessage } from '../protocol.js';
import { makeResponse, makeError, ErrorCode } from '../protocol.js';
import type { Config } from '../../config/index.js';

export interface ConfigHandlerDeps {
  config: Config;
}

/**
 * Redact sensitive values from config before returning.
 * Replaces API keys, tokens, passwords, and other credentials with "***".
 *
 * Covers: telegram, discord, slack, matrix, server, models (tiers + fallbacks + local_providers),
 * web_search, audio, memory.embedding, automation (webhooks + gmail), and mcp server env vars.
 */
export function redactConfig(config: Config): Record<string, unknown> {
  const raw = JSON.parse(JSON.stringify(config)) as Record<string, unknown>;

  // Helper: redact specified keys on an object if they exist and are non-nullish
  const redact = (obj: Record<string, unknown> | undefined, ...keys: string[]) => {
    if (!obj) {return;}
    for (const key of keys) {
      if (obj[key] !== undefined && obj[key] !== null) {obj[key] = '***';}
    }
  };

  // Telegram
  redact(raw.telegram as Record<string, unknown>, 'bot_token');

  // Discord
  redact(raw.discord as Record<string, unknown>, 'bot_token');

  // Slack
  redact(raw.slack as Record<string, unknown>, 'bot_token', 'app_token', 'signing_secret');

  // Matrix
  redact(raw.matrix as Record<string, unknown>, 'access_token');

  // Server (gateway bearer token)
  redact(raw.server as Record<string, unknown>, 'token');

  // Models — tiers, their fallbacks, and local_providers (+ their fallbacks)
  const models = raw.models as Record<string, unknown> | undefined;
  if (models) {
    for (const tier of ['default', 'fast', 'complex', 'local']) {
      const m = models[tier] as Record<string, unknown> | undefined;
      redact(m, 'api_key', 'auth_token');
      const fb = m?.fallback as Record<string, unknown> | undefined;
      redact(fb, 'api_key', 'auth_token');
    }
    const localProviders = models.local_providers as Record<string, Record<string, unknown>> | undefined;
    if (localProviders) {
      for (const provider of Object.values(localProviders)) {
        redact(provider, 'api_key', 'auth_token');
        const fb = provider.fallback as Record<string, unknown> | undefined;
        redact(fb, 'api_key', 'auth_token');
      }
    }
  }

  // Web search
  redact(raw.web_search as Record<string, unknown>, 'api_key');

  // Audio
  redact(raw.audio as Record<string, unknown>, 'transcription_api_key');

  // Memory → embedding
  const memory = raw.memory as Record<string, unknown> | undefined;
  if (memory) {
    redact(memory.embedding as Record<string, unknown>, 'api_key');
  }

  // Automation — webhook HMAC secrets and gmail credential paths
  const automation = raw.automation as Record<string, unknown> | undefined;
  if (automation) {
    const webhooks = automation.webhooks as Record<string, unknown>[] | undefined;
    if (webhooks) {
      for (const wh of webhooks) {
        redact(wh, 'secret');
      }
    }
    const gmail = automation.gmail as Record<string, unknown> | undefined;
    redact(gmail, 'credentials_file', 'token_file');
  }

  // MCP server env vars (may contain API keys or other secrets)
  const mcp = raw.mcp as Record<string, unknown> | undefined;
  if (mcp) {
    const servers = mcp.servers as Record<string, unknown>[] | undefined;
    if (servers) {
      for (const srv of servers) {
        if (srv.env && typeof srv.env === 'object') {
          const env = srv.env as Record<string, unknown>;
          for (const key of Object.keys(env)) {
            env[key] = '***';
          }
        }
      }
    }
  }

  return raw;
}

/** Keys that are safe to update at runtime via config.patch. */
const PATCHABLE_KEYS: Record<string, (config: Config, value: unknown) => boolean> = {
  'hooks.confirm': (config, value) => {
    if (!Array.isArray(value) || !value.every((v) => typeof v === 'string')) {return false;}
    config.hooks.confirm = value as string[];
    return true;
  },
  'hooks.log': (config, value) => {
    if (!Array.isArray(value) || !value.every((v) => typeof v === 'string')) {return false;}
    config.hooks.log = value as string[];
    return true;
  },
  'hooks.silent': (config, value) => {
    if (!Array.isArray(value) || !value.every((v) => typeof v === 'string')) {return false;}
    config.hooks.silent = value as string[];
    return true;
  },
  'server.localhost': (config, value) => {
    if (typeof value !== 'boolean') {return false;}
    config.server.localhost = value;
    return true;
  },
};

export function createConfigHandlers(deps: ConfigHandlerDeps) {
  return {
    'config.get': async (request: GatewayRequest): Promise<OutboundMessage> => {
      return makeResponse(request.id, redactConfig(deps.config));
    },

    'config.patch': async (request: GatewayRequest): Promise<OutboundMessage> => {
      const patches = request.params?.patches;
      if (!patches || typeof patches !== 'object' || Array.isArray(patches)) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'params.patches must be an object of { key: value } pairs');
      }

      const applied: string[] = [];
      const rejected: string[] = [];

      for (const [key, value] of Object.entries(patches as Record<string, unknown>)) {
        const patcher = PATCHABLE_KEYS[key];
        if (!patcher) {
          rejected.push(key);
          continue;
        }
        const ok = patcher(deps.config, value);
        if (ok) {
          applied.push(key);
        } else {
          rejected.push(key);
        }
      }

      return makeResponse(request.id, { applied, rejected });
    },
  };
}
