'use strict';

const McpPhase = Object.freeze({
  CONFIG_LOAD: 'config_load',
  SERVER_REGISTRATION: 'server_registration',
  SPAWN_CONNECT: 'spawn_connect',
  INITIALIZE_HANDSHAKE: 'initialize_handshake',
  TOOL_DISCOVERY: 'tool_discovery',
  RESOURCE_DISCOVERY: 'resource_discovery',
  READY: 'ready',
  INVOCATION: 'invocation',
  ERROR_SURFACING: 'error_surfacing',
  SHUTDOWN: 'shutdown',
  CLEANUP: 'cleanup',
});

const McpStatus = Object.freeze({
  PENDING: 'pending',
  RUNNING: 'running',
  READY: 'ready',
  DEGRADED: 'degraded',
  FAILED: 'failed',
  SHUTDOWN: 'shutdown',
});

const RECOVERABLE_PHASES = new Set([
  McpPhase.SPAWN_CONNECT,
  McpPhase.INITIALIZE_HANDSHAKE,
  McpPhase.TOOL_DISCOVERY,
  McpPhase.RESOURCE_DISCOVERY,
  McpPhase.INVOCATION,
]);

class McpLifecycleTracker {
  constructor({ serverName, config = null, clock = () => new Date().toISOString() } = {}) {
    if (!serverName) throw new Error('serverName is required');
    this.serverName = serverName;
    this.config = sanitizeConfig(config);
    this.clock = clock;
    this.status = McpStatus.PENDING;
    this.phase = null;
    this.events = [];
    this.errors = [];
    this.tools = [];
    this.resources = [];
  }

  enter(phase, details = {}) {
    const normalized = normalizePhase(phase);
    this.phase = normalized;
    this.status = normalized === McpPhase.READY ? McpStatus.READY : McpStatus.RUNNING;
    const event = this._event('phase', normalized, details);
    this.events.push(event);
    return event;
  }

  ready(details = {}) {
    this.phase = McpPhase.READY;
    this.status = McpStatus.READY;
    const event = this._event('ready', McpPhase.READY, details);
    this.events.push(event);
    return event;
  }

  fail(phase, error, details = {}) {
    const normalized = normalizePhase(phase);
    const recoverable = details.recoverable ?? RECOVERABLE_PHASES.has(normalized);
    this.phase = normalized;
    this.status = recoverable ? McpStatus.DEGRADED : McpStatus.FAILED;
    const surface = {
      phase: normalized,
      serverName: this.serverName,
      message: error?.message || String(error || 'MCP failure'),
      code: error?.code ? String(error.code) : '',
      recoverable: Boolean(recoverable),
      context: sanitizeContext(details.context || details),
      timestamp: this.clock(),
    };
    this.errors.push(surface);
    const event = this._event('error', normalized, surface);
    this.events.push(event);
    return surface;
  }

  setTools(tools = []) {
    this.tools = Array.isArray(tools) ? tools.map((tool) => ({
      name: String(tool.name || ''),
      description: String(tool.description || ''),
    })) : [];
    return this.tools;
  }

  setResources(resources = []) {
    this.resources = Array.isArray(resources) ? resources.map((resource) => ({
      uri: String(resource.uri || ''),
      name: String(resource.name || ''),
    })) : [];
    return this.resources;
  }

  snapshot() {
    return {
      serverName: this.serverName,
      config: this.config,
      status: this.status,
      phase: this.phase,
      toolCount: this.tools.length,
      resourceCount: this.resources.length,
      tools: this.tools.map((tool) => ({ ...tool })),
      resources: this.resources.map((resource) => ({ ...resource })),
      errors: this.errors.map((error) => ({ ...error, context: { ...(error.context || {}) } })),
      events: this.events.map((event) => ({ ...event, details: { ...(event.details || {}) } })),
    };
  }

  _event(type, phase, details) {
    return {
      type,
      serverName: this.serverName,
      phase,
      status: this.status,
      timestamp: this.clock(),
      details: sanitizeContext(details || {}),
    };
  }
}

class McpLifecycleRegistry {
  constructor({ clock = () => new Date().toISOString() } = {}) {
    this.clock = clock;
    this.trackers = new Map();
  }

  get(serverName, config = null) {
    if (!serverName) throw new Error('serverName is required');
    if (!this.trackers.has(serverName)) {
      this.trackers.set(serverName, new McpLifecycleTracker({ serverName, config, clock: this.clock }));
    }
    return this.trackers.get(serverName);
  }

  registerConfig(serverName, config = {}) {
    const tracker = this.get(serverName, config);
    tracker.config = sanitizeConfig(config);
    tracker.enter(McpPhase.CONFIG_LOAD, { configured: true });
    tracker.enter(McpPhase.SERVER_REGISTRATION, { type: config.type || (config.command ? 'stdio' : 'unknown') });
    return tracker;
  }

  report() {
    const servers = [...this.trackers.values()].map((tracker) => tracker.snapshot());
    return buildMcpDegradedReport(servers);
  }

  clear() {
    this.trackers.clear();
  }
}

function buildMcpDegradedReport(servers = []) {
  const working = [];
  const degraded = [];
  const failed = [];
  for (const server of servers) {
    if (server.status === McpStatus.READY) working.push(server);
    else if (server.status === McpStatus.DEGRADED) degraded.push(server);
    else if (server.status === McpStatus.FAILED) failed.push(server);
  }
  return {
    ok: failed.length === 0,
    degraded: degraded.length > 0 || failed.length > 0,
    working,
    failed,
    degradedServers: degraded,
    servers,
    summary: summarizeReport({ working, degraded, failed, total: servers.length }),
  };
}

function summarizeReport({ working, degraded, failed, total }) {
  if (!total) return 'No MCP servers configured.';
  if (!degraded.length && !failed.length) return `${working.length}/${total} MCP servers ready.`;
  return `${working.length}/${total} MCP servers ready; ${degraded.length} degraded; ${failed.length} failed.`;
}

function normalizePhase(phase) {
  const value = String(phase || '').toLowerCase();
  if (Object.values(McpPhase).includes(value)) return value;
  return McpPhase.ERROR_SURFACING;
}

function sanitizeConfig(config = {}) {
  if (!config || typeof config !== 'object') return {};
  return {
    name: String(config.name || ''),
    type: String(config.type || (config.command ? 'stdio' : '')),
    command: config.command ? String(config.command) : '',
    url: config.url ? redactUrl(config.url) : '',
    hasOAuth: Boolean(config.oauth),
    hasHeaders: Boolean(config.headers && Object.keys(config.headers).length),
  };
}

function sanitizeContext(value = {}) {
  const out = {};
  for (const [key, raw] of Object.entries(value || {})) {
    if (/token|secret|authorization|password|key/i.test(key)) {
      out[key] = '[redacted]';
    } else if (raw instanceof Error) {
      out[key] = raw.message;
    } else if (typeof raw === 'function') {
      out[key] = '[function]';
    } else if (raw && typeof raw === 'object') {
      out[key] = '[object]';
    } else {
      out[key] = raw;
    }
  }
  return out;
}

function redactUrl(raw) {
  try {
    const url = new URL(raw);
    url.username = '';
    url.password = '';
    return url.toString();
  } catch {
    return String(raw).replace(/\/\/[^/@]+@/, '//[redacted]@');
  }
}

const defaultMcpLifecycleRegistry = new McpLifecycleRegistry();

module.exports = {
  McpLifecycleRegistry,
  McpLifecycleTracker,
  McpPhase,
  McpStatus,
  buildMcpDegradedReport,
  defaultMcpLifecycleRegistry,
};
