import EventSource from 'eventsource';

export class Router {
  constructor(bot, channelUrl, channelSecret) {
    this._bot = bot;
    this._url = channelUrl.replace(/\/$/, '');
    this._secret = channelSecret;
    this._streams = new Map();  // conversationId → EventSource
    this._chatIds = new Map();  // conversationId → chatId
    this._seen = new Map();     // conversationId → Set<messageId>
  }

  start() {
    this._bot.on('message', ({ userId, chatId, text }) => {
      const convId = `telegram-${userId}`;
      this._chatIds.set(convId, chatId);
      this._ensureStream(convId, chatId);
      this._postMessage(convId, userId, text);
    });

    this._bot.on('photo', ({ userId, chatId, caption, base64 }) => {
      const convId = `telegram-${userId}`;
      this._chatIds.set(convId, chatId);
      this._ensureStream(convId, chatId);
      const text = caption ? `[Photo] ${caption}` : '[Photo attached]';
      this._postMessage(convId, userId, text, base64);
    });
  }

  async _postMessage(conversationId, userId, text, imageBase64 = null) {
    const body = {
      text,
      conversation_id: conversationId,
      user_email: `telegram:${userId}`,
    };
    if (imageBase64) body.image_base64 = imageBase64;

    try {
      const res = await fetch(`${this._url}/message`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${this._secret}`,
        },
        body: JSON.stringify(body),
      });
      if (!res.ok) {
        const txt = await res.text().catch(() => '');
        console.error(`[router] Channel POST failed (${res.status}):`, txt);
      }
    } catch (err) {
      console.error('[router] Channel POST error:', err.message);
    }
  }

  _ensureStream(conversationId, chatId) {
    if (this._streams.has(conversationId)) return;

    const url = `${this._url}/stream?conversation_id=${encodeURIComponent(conversationId)}`;
    const es = new EventSource(url);
    this._streams.set(conversationId, es);
    this._seen.set(conversationId, new Set());

    const seen = this._seen.get(conversationId);

    const handleEvent = (event) => {
      let data;
      try { data = JSON.parse(event.data); } catch { return; }
      if (data.role !== 'assistant' || !data.text) return;

      // Deduplicate by message id, falling back to content hash
      const msgId = data.id || data.timestamp || data.text.slice(0, 60);
      if (seen.has(msgId)) return;
      seen.add(msgId);

      const html = agentToTelegramHtml(data.text);
      this._bot
        .sendText(chatId, html, { parse_mode: 'HTML' })
        .catch(() => this._bot.sendText(chatId, data.text).catch(() => {}));
    };

    es.addEventListener('message', handleEvent);

    es.onerror = () => {
      console.error(`[router] SSE disconnected for ${conversationId}, will reconnect on next message`);
      es.close();
      this._streams.delete(conversationId);
    };
  }

  stop() {
    for (const es of this._streams.values()) es.close();
    this._streams.clear();
  }
}

function htmlEscape(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}

function agentToTelegramHtml(text) {
  // Extract and protect code blocks before any other processing
  const codeBlocks = [];
  let result = text.replace(/```(?:\w+)?\n?([\s\S]*?)```/g, (_, code) => {
    const idx = codeBlocks.length;
    codeBlocks.push(`<pre><code>${htmlEscape(code.trimEnd())}</code></pre>`);
    return `\x00BLK${idx}\x00`;
  });

  const codeSpans = [];
  result = result.replace(/`([^`\n]+)`/g, (_, code) => {
    const idx = codeSpans.length;
    codeSpans.push(`<code>${htmlEscape(code)}</code>`);
    return `\x00SPN${idx}\x00`;
  });

  // HTML-escape the remaining text, then apply markdown formatting
  result = htmlEscape(result);
  result = result
    .replace(/\*\*(.+?)\*\*/gs, '<b>$1</b>')
    .replace(/\*(.+?)\*/gs, '<i>$1</i>')
    .replace(/_(.+?)_/gs, '<i>$1</i>')
    .replace(/\[(.+?)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2">$1</a>');

  // Restore code blocks
  result = result.replace(/\x00BLK(\d+)\x00/g, (_, i) => codeBlocks[+i]);
  result = result.replace(/\x00SPN(\d+)\x00/g, (_, i) => codeSpans[+i]);

  return result;
}
