/**
 * Tests for telegramBlockParser.
 *
 * The parser is the bridge between "agent emits raw text" and "Telegram
 * renders rich messages with buttons + images". Each test locks one
 * branch of the contract so a refactor can't silently drop a feature.
 */

import { describe, test, expect } from '@jest/globals';
import { parseTelegramBlock } from '../telegramBlockParser.js';

describe('parseTelegramBlock — plain text passthrough', () => {
  test('returns unchanged text when no tags present', () => {
    const r = parseTelegramBlock('Hello world');
    expect(r.text).toBe('Hello world');
    expect(r.actions).toEqual([]);
    expect(r.media).toEqual([]);
  });

  test('handles empty / non-string input safely', () => {
    expect(parseTelegramBlock('')).toEqual({ text: '', actions: [], media: [] });
    expect(parseTelegramBlock(null)).toEqual({ text: '', actions: [], media: [] });
    expect(parseTelegramBlock(undefined)).toEqual({ text: '', actions: [], media: [] });
    expect(parseTelegramBlock(42)).toEqual({ text: '', actions: [], media: [] });
  });

  test('collapses runs of blank lines left by stripped tags', () => {
    const r = parseTelegramBlock('Line A\n\n<actions>[]</actions>\n\n\n\nLine B');
    expect(r.text).toBe('Line A\n\nLine B');
  });
});

describe('parseTelegramBlock — <actions> blocks', () => {
  test('parses a list of buttons', () => {
    const r = parseTelegramBlock(
      'Pick one:\n<actions>[{"label":"Continue","value":"go"},{"label":"Stop","value":"halt"}]</actions>'
    );
    expect(r.text).toBe('Pick one:');
    expect(r.actions).toEqual([
      { label: 'Continue', value: 'go' },
      { label: 'Stop', value: 'halt' },
    ]);
  });

  test('value defaults to label when only label is provided', () => {
    const r = parseTelegramBlock('<actions>[{"label":"Yes"},{"label":"No"}]</actions>');
    expect(r.actions).toEqual([
      { label: 'Yes', value: 'Yes' },
      { label: 'No', value: 'No' },
    ]);
  });

  test('truncates oversized labels (Telegram caps button text)', () => {
    const longLabel = 'x'.repeat(200);
    const r = parseTelegramBlock(`<actions>[{"label":"${longLabel}","value":"v"}]</actions>`);
    expect(r.actions[0].label.length).toBe(64);
  });

  test('malformed JSON inside <actions> is silently dropped, body preserved', () => {
    const r = parseTelegramBlock('Stay calm.\n<actions>{not json}</actions>');
    expect(r.text).toBe('Stay calm.');
    expect(r.actions).toEqual([]);
  });

  test('non-array JSON inside <actions> drops the block', () => {
    const r = parseTelegramBlock('<actions>{"label":"X"}</actions>');
    expect(r.actions).toEqual([]);
  });

  test('multiple <actions> blocks concatenate in document order', () => {
    const r = parseTelegramBlock(
      '<actions>[{"label":"A"}]</actions> mid <actions>[{"label":"B"}]</actions>'
    );
    expect(r.actions.map(b => b.label)).toEqual(['A', 'B']);
    expect(r.text).toBe('mid');
  });

  test('items missing a "label" string are dropped', () => {
    const r = parseTelegramBlock(
      '<actions>[{"value":"x"},{"label":42},{"label":"OK"}]</actions>'
    );
    expect(r.actions).toEqual([{ label: 'OK', value: 'OK' }]);
  });
});

describe('parseTelegramBlock — <image> blocks', () => {
  test('parses src + caption', () => {
    const r = parseTelegramBlock(
      'Look at this:\n<image src="https://example.com/cat.png">A cat</image>'
    );
    expect(r.text).toBe('Look at this:');
    expect(r.media).toEqual([{
      kind: 'image',
      src: 'https://example.com/cat.png',
      caption: 'A cat',
      alt: null,
      type: null,
    }]);
  });

  test('parses src + alt attribute', () => {
    const r = parseTelegramBlock('<image src="x.png" alt="placeholder"></image>');
    expect(r.media[0].alt).toBe('placeholder');
  });

  test('accepts single-quoted attributes', () => {
    const r = parseTelegramBlock("<image src='x.png' alt='ok'>caption</image>");
    expect(r.media[0].src).toBe('x.png');
    expect(r.media[0].alt).toBe('ok');
    expect(r.media[0].caption).toBe('caption');
  });

  test('image without src is dropped (corrupt agent output)', () => {
    const r = parseTelegramBlock('<image alt="no src">missing</image>');
    expect(r.media).toEqual([]);
    expect(r.text).toBe('');
  });

  test('case-insensitive tag matching', () => {
    const r = parseTelegramBlock('<IMAGE SRC="x.png">cap</IMAGE>');
    expect(r.media).toHaveLength(1);
    expect(r.media[0].src).toBe('x.png');
  });
});

describe('parseTelegramBlock — <attachment> blocks', () => {
  test('parses src + type + caption', () => {
    const r = parseTelegramBlock(
      '<attachment src="/tmp/report.pdf" type="application/pdf">Q4 report</attachment>'
    );
    expect(r.media).toEqual([{
      kind: 'document',
      src: '/tmp/report.pdf',
      caption: 'Q4 report',
      alt: null,
      type: 'application/pdf',
    }]);
  });

  test('type is optional', () => {
    const r = parseTelegramBlock('<attachment src="/tmp/x.bin"></attachment>');
    expect(r.media[0].type).toBeNull();
  });

  test('attachment without src is dropped', () => {
    const r = parseTelegramBlock('<attachment type="text/plain">no src</attachment>');
    expect(r.media).toEqual([]);
  });
});

describe('parseTelegramBlock — composite payloads', () => {
  test('text + actions + image + attachment all together', () => {
    const body =
      'Status: done.\n' +
      '<image src="chart.png">Bar chart</image>\n' +
      '<attachment src="report.pdf" type="application/pdf">PDF</attachment>\n' +
      '<actions>[{"label":"Continue"},{"label":"Restart","value":"reset"}]</actions>';
    const r = parseTelegramBlock(body);

    expect(r.text).toBe('Status: done.');
    expect(r.media).toHaveLength(2);
    expect(r.media[0].kind).toBe('image');
    expect(r.media[1].kind).toBe('document');
    expect(r.actions).toEqual([
      { label: 'Continue', value: 'Continue' },
      { label: 'Restart',  value: 'reset' },
    ]);
  });

  test('whitespace-only body becomes empty text', () => {
    const r = parseTelegramBlock('   \n\n   ');
    expect(r.text).toBe('');
  });

  test('order of tags inside body is preserved-as-stripped', () => {
    // The TEXT result only contains the non-tag parts, in original order.
    const r = parseTelegramBlock(
      'A <image src="i.png">cap</image> B <actions>[]</actions> C'
    );
    expect(r.text).toBe('A  B  C');
    expect(r.media).toHaveLength(1);
  });
});
