var axios = require('axios');

var MCP_URLS = buildMcpUrls();
var _lastGoodMcpUrl = MCP_URLS[0];
var MCP_INITIALIZE_TIMEOUT_MS = positiveInt(process.env.CLINICAL_ASSISTANT_MCP_INITIALIZE_TIMEOUT_MS, 30000);
var MCP_REQUEST_TIMEOUT_MS = positiveInt(process.env.CLINICAL_ASSISTANT_MCP_REQUEST_TIMEOUT_MS, 90000);
var MCP_SESSION_TTL_MS = positiveInt(process.env.CLINICAL_ASSISTANT_MCP_SESSION_TTL_MS, 10 * 60 * 1000);
var _mcpSession = null;
var _mcpSessionPromise = null;
var _mcpCallQueue = Promise.resolve();
var _mcpRequestId = 1;

function positiveInt(value, fallback) {
  var n = Number(value);
  return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
}

async function semanticSearch(query, opts) {
  opts = opts || {};
  return callMcpTool('nc_semantic_search', {
    query: query,
    limit: opts.limit,
    doc_types: ['file'],
    score_threshold: 0,
    fusion: 'rrf',
    include_context: opts.includeContext,
    context_chars: opts.contextChars
  });
}

async function multimodalSearch(query, opts) {
  opts = opts || {};
  return callMcpTool('nc_multimodal_search', {
    query: query,
    limit: opts.limit
  });
}

async function indexedTopicSuggestions(limit) {
  return callMcpTool('nc_indexed_topic_suggestions', {
    limit: limit || 12,
    sample_size: 1500,
    doc_type: 'file'
  });
}

function warmMcpSession() {
  return getMcpSession();
}

async function callMcpTool(name, args) {
  var queued = _mcpCallQueue.then(function() {
    return callMcpToolUnlocked(name, args);
  });
  _mcpCallQueue = queued.catch(function() {});
  return queued;
}

async function callMcpToolUnlocked(name, args) {
  var session = await getMcpSession();
  var payload = {
    jsonrpc: '2.0',
    id: nextMcpRequestId(),
    method: 'tools/call',
    params: {
      name: name,
      arguments: args
    }
  };
  var search;
  try {
    search = await mcpRequest(payload, session.sessionId, session.mcpUrl);
  } catch (e) {
    if (!isInvalidMcpSessionError(e)) throw e;
    _mcpSession = null;
    session = await getMcpSession();
    payload.id = nextMcpRequestId();
    search = await mcpRequest(payload, session.sessionId, session.mcpUrl);
  }
  return search.result || search;
}

function nextMcpRequestId() {
  _mcpRequestId += 1;
  if (_mcpRequestId > 1000000000) _mcpRequestId = 2;
  return _mcpRequestId;
}

async function getMcpSession() {
  var now = Date.now();
  if (_mcpSession && _mcpSession.sessionId && _mcpSession.expiresAt > now) return _mcpSession;
  if (_mcpSessionPromise) return _mcpSessionPromise;
  _mcpSessionPromise = initializeMcpSession().then(function(session) {
    _mcpSession = session;
    return session;
  }).finally(function() {
    _mcpSessionPromise = null;
  });
  return _mcpSessionPromise;
}

async function initializeMcpSession() {
  var session = await mcpRequest({
    jsonrpc: '2.0',
    id: nextMcpRequestId(),
    method: 'initialize',
    params: {
      protocolVersion: '2024-11-05',
      capabilities: {},
      clientInfo: { name: 'ped-ai-clinical-assistant', version: '1.0.0' }
    }
  });
  session.expiresAt = Date.now() + MCP_SESSION_TTL_MS;
  return session;
}

async function mcpRequest(payload, sessionId, preferredUrl) {
  var headers = {
    'Accept': 'application/json, text/event-stream',
    'Content-Type': 'application/json'
  };
  if (sessionId) headers['mcp-session-id'] = sessionId;
  var resp = await postMcpWithRetry(payload, headers, preferredUrl);
  var parsed = parseMcpResponse(resp.data);
  if (parsed.error) throw new Error(parsed.error.message || JSON.stringify(parsed.error));
  parsed.sessionId = resp.headers['mcp-session-id'] || sessionId || null;
  parsed.mcpUrl = resp.config && resp.config.url ? resp.config.url : preferredUrl || _lastGoodMcpUrl;
  return parsed;
}

async function postMcpWithRetry(payload, headers, preferredUrl) {
  var lastErr = null;
  var urls = orderedMcpUrls(preferredUrl);
  var isInitialize = payload && payload.method === 'initialize';
  for (var round = 0; round < 2; round++) {
    for (var i = 0; i < urls.length; i++) {
      var url = urls[i];
      try {
        var resp = await axios.post(url, payload, {
          headers: headers,
          timeout: isInitialize ? MCP_INITIALIZE_TIMEOUT_MS : MCP_REQUEST_TIMEOUT_MS,
          responseType: 'text',
          transformResponse: [function(data) { return data; }]
        });
        _lastGoodMcpUrl = url;
        return resp;
      } catch (e) {
        lastErr = e;
        if (!isTransientMcpError(e)) throw e;
        console.warn('[clinical-assistant] MCP endpoint unavailable:', url, e.code || e.message);
      }
    }
    await sleep(750 * (round + 1));
  }
  throw lastErr;
}

function orderedMcpUrls(preferredUrl) {
  var candidates = [];
  [preferredUrl, _lastGoodMcpUrl].concat(MCP_URLS).forEach(function(url) {
    if (url && candidates.indexOf(url) === -1) candidates.push(url);
  });
  return candidates;
}

function isTransientMcpError(e) {
  var code = e && (e.code || (e.cause && e.cause.code)) || '';
  var status = e && e.response && e.response.status;
  return code === 'ECONNREFUSED' || code === 'ECONNRESET' || code === 'ETIMEDOUT' || status === 502 || status === 503 || status === 504;
}

function isInvalidMcpSessionError(e) {
  var status = e && e.response && e.response.status;
  var msg = String(e && e.message || '');
  return status === 400 || status === 404 || status === 410 || /session|mcp-session-id/i.test(msg);
}

async function getMcpHealth() {
  var results = [];
  var urls = orderedMcpUrls();
  for (var i = 0; i < urls.length; i++) {
    var mcpUrl = urls[i];
    var healthUrl = mcpUrl.replace(/\/mcp\/?$/, '/health/live');
    try {
      var resp = await axios.get(healthUrl, { timeout: 3000 });
      results.push({ url: mcpUrl, healthUrl: healthUrl, ok: true, status: resp.status, data: resp.data });
      _lastGoodMcpUrl = mcpUrl;
      return { ok: true, activeUrl: mcpUrl, checked: results, candidates: MCP_URLS };
    } catch (e) {
      results.push({ url: mcpUrl, healthUrl: healthUrl, ok: false, error: e.code || e.message });
    }
  }
  return { ok: false, activeUrl: _lastGoodMcpUrl || null, checked: results, candidates: MCP_URLS };
}

function buildMcpUrls() {
  var raw = [];
  if (process.env.CLINICAL_ASSISTANT_MCP_URLS) raw = raw.concat(process.env.CLINICAL_ASSISTANT_MCP_URLS.split(','));
  raw.push(process.env.CLINICAL_ASSISTANT_MCP_URL || process.env.MCP_SERVER_URL || '');
  raw.push('http://mcp:8000/mcp');
  raw.push('http://mcp-server-mcp-1:8000/mcp');
  raw.push('http://127.0.0.1:8100/mcp');
  var out = [];
  raw.forEach(function(url) {
    url = String(url || '').trim();
    if (!url) return;
    if (!/\/mcp\/?$/.test(url)) url = url.replace(/\/$/, '') + '/mcp';
    if (out.indexOf(url) === -1) out.push(url);
  });
  return out;
}

function sleep(ms) {
  return new Promise(function(resolve) { setTimeout(resolve, ms); });
}

function parseMcpResponse(body) {
  var text = String(body || '').trim();
  if (!text) return {};
  if (text.charAt(0) === '{') return JSON.parse(text);
  var lines = text.split(/\r?\n/);
  for (var i = 0; i < lines.length; i++) {
    if (lines[i].indexOf('data:') === 0) {
      return JSON.parse(lines[i].substring(5).trim());
    }
  }
  throw new Error('Unexpected MCP response format');
}

module.exports = {
  semanticSearch: semanticSearch,
  multimodalSearch: multimodalSearch,
  indexedTopicSuggestions: indexedTopicSuggestions,
  getMcpHealth: getMcpHealth,
  warmMcpSession: warmMcpSession
};
