import { describe, it, expect, beforeAll } from 'bun:test';
import { cleanText, convertMillisecondsToTimeString, convertSecondsToTimeString, coloredTextCodeBlock } from './markdown';
import * as path from 'path';
import * as fs from 'fs';

// Load test fixtures
const basePath = path.join(__dirname, '../../fixtures/markdown');
const markupFixturesPath = path.join(basePath, 'test-data-markup.json');
const timeFixturesPath = path.join(basePath, 'test-data-time.json');
const colorFixturesPath = path.join(basePath, 'test-data-colors.json');

let markupFixtures: any;
let timeFixtures: any;
let colorFixtures: any;

describe('cleanText', () => {
  beforeAll(() => {
    // Fixtures are already loaded above
    markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
    timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
    colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
  });
  it('should handle basic bold markup', () => {
    const input = markupFixtures.boldMarkup.complete;
    const result = cleanText(input);
    expect(result).toBe('**bold text**');
  });

  it('should handle incomplete bold markup - open only', () => {
    const input = markupFixtures.boldMarkup.openOnly;
    const result = cleanText(input);
    expect(result).toBe('**bold text**');
  });

  it('should handle incomplete bold markup - close only', () => {
    const input = markupFixtures.boldMarkup.closeOnly;
    const result = cleanText(input);
    expect(result).toBe('**bold text**');
  });

  it('should handle basic italic markup', () => {
    const input = markupFixtures.italicMarkup.complete;
    const result = cleanText(input);
    expect(result).toBe('*italic text*');
  });

  it('should handle incomplete italic markup - open only', () => {
    const input = markupFixtures.italicMarkup.openOnly;
    const result = cleanText(input);
    expect(result).toBe('*italic text*');
  });

  it('should handle incomplete italic markup - close only', () => {
    const input = markupFixtures.italicMarkup.closeOnly;
    const result = cleanText(input);
    expect(result).toBe('*italic text*');
  });

  it('should remove color tags with hex colors', () => {
    const input = markupFixtures.colorTags.hex6;
    const result = cleanText(input);
    expect(result).toBe('colored text');
  });

  it('should remove color tags with hex colors (8-digit)', () => {
    const input = markupFixtures.colorTags.hex8;
    const result = cleanText(input);
    expect(result).toBe('colored text');
  });

  it('should remove color tags with named colors', () => {
    const input = markupFixtures.colorTags.namedColor;
    const result = cleanText(input);
    expect(result).toBe('colored text');
  });

  it('should convert EVE links to Discord format', () => {
    const input = markupFixtures.eveLinks.simple;
    const result = cleanText(input);
    expect(result).toBe('[Rifter](https://everef.net/types/587)');
  });

  it('should handle EVE links with spaces in text', () => {
    const input = markupFixtures.eveLinks.withSpaces;
    const result = cleanText(input);
    expect(result).toBe('[Ship Name With Spaces](https://everef.net/types/12345)');
  });

  it('should handle complex markup combinations', () => {
    const input = markupFixtures.combined.allMarkup;
    const result = cleanText(input);
    expect(result).toBe('**Bold** *italic* colored [linked](https://everef.net/types/587)');
  });

  it('should respect max length parameter', () => {
    const longText = 'a'.repeat(100);
    const input = `<b>${longText}</b>`;
    const result = cleanText(input, 50);
    expect(result.length).toBeLessThanOrEqual(53); // Account for ** markup + truncation
    expect(result).toContain('**');
  });

  it('should use default max length when not specified', () => {
    const veryLongText = 'a'.repeat(2000);
    const input = `<b>${veryLongText}</b>`;
    const result = cleanText(input);
    expect(result.length).toBeLessThanOrEqual(1003); // Account for ** markup
  });

  it('should handle empty input', () => {
    const result = cleanText('');
    expect(result).toBe('');
  });

  it('should handle whitespace-only input', () => {
    const result = cleanText('   \n\t  ');
    expect(result).toBe('');
  });

  it('should trim whitespace from input', () => {
    const input = '  <b>text</b>  ';
    const result = cleanText(input);
    expect(result).toBe('**text**');
  });

  it('should handle multiple bold tags', () => {
    const input = markupFixtures.boldMarkup.multiple;
    const result = cleanText(input);
    expect(result).toBe('**first** and **second**');
  });

  it('should handle multiple italic tags', () => {
    const input = markupFixtures.italicMarkup.multiple;
    const result = cleanText(input);
    expect(result).toBe('*first* and *second*');
  });

  it('should handle multiple color tags', () => {
    const input = markupFixtures.colorTags.multiple;
    const result = cleanText(input);
    expect(result).toBe('first and second');
  });

  it('should handle multiple EVE links', () => {
    const input = markupFixtures.eveLinks.multiple;
    const result = cleanText(input);
    expect(result).toBe('[Rifter](https://everef.net/types/587) and [Merlin](https://everef.net/types/588)');
  });

  it('should handle nested markup', () => {
    const input = markupFixtures.boldMarkup.nested;
    const result = cleanText(input);
    expect(result).toContain('**');
  });

  it('should handle empty tags', () => {
    const result1 = cleanText(markupFixtures.boldMarkup.empty);
    const result2 = cleanText(markupFixtures.italicMarkup.empty);
    const result3 = cleanText(markupFixtures.colorTags.empty);

    // The regex doesn't match empty content between tags
    expect(result1).toBe('<b></b>'); // No match, so unchanged
    expect(result2).toBe('<i></i>'); // No match, so unchanged
    expect(result3).toBe('');
  });
});

describe('convertMillisecondsToTimeString', () => {
  beforeAll(() => {
    // Fixtures are already loaded above
    markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
    timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
    colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
  });
  it('should handle zero milliseconds', () => {
    const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.zero);
    expect(result).toBe(timeFixtures.expected.zero);
  });

  it('should handle one second', () => {
    const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.oneSecond);
    expect(result).toBe(timeFixtures.expected.oneSecond);
  });

  it('should handle one minute', () => {
    const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.oneMinute);
    expect(result).toBe(timeFixtures.expected.oneMinute);
  });

  it('should handle one hour', () => {
    const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.oneHour);
    expect(result).toBe(timeFixtures.expected.oneHour);
  });

  it('should handle complex time (1h 1m 1.5s)', () => {
    const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.complex);
    expect(result).toBe(timeFixtures.expected.complexMs);
  });

  it('should handle large time values (24h)', () => {
    const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.daysWorthMs);
    expect(result).toBe(timeFixtures.expected.daysMs);
  });

  it('should handle fractional seconds', () => {
    const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.fractionalSeconds);
    expect(result).toBe(timeFixtures.expected.fractionalSeconds);
  });

  it('should handle small fractions', () => {
    const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.smallFraction);
    expect(result).toBe(timeFixtures.expected.smallFraction);
  });

  it('should handle negative values', () => {
    const result = convertMillisecondsToTimeString(-1000);
    expect(result).toBe('-1.0s');
  });
});

describe('convertSecondsToTimeString', () => {
  beforeAll(() => {
    // Fixtures are already loaded above
    markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
    timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
    colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
  });
  it('should handle zero seconds', () => {
    const result = convertSecondsToTimeString(timeFixtures.seconds.zero);
    expect(result).toBe('0s');
  });

  it('should handle one second', () => {
    const result = convertSecondsToTimeString(timeFixtures.seconds.oneSecond);
    expect(result).toBe('1s');
  });

  it('should handle one minute', () => {
    const result = convertSecondsToTimeString(timeFixtures.seconds.oneMinute);
    expect(result).toBe('1m');
  });

  it('should handle one hour', () => {
    const result = convertSecondsToTimeString(timeFixtures.seconds.oneHour);
    expect(result).toBe('1h');
  });

  it('should handle complex time (1h 1m 1s)', () => {
    const result = convertSecondsToTimeString(timeFixtures.seconds.complex);
    expect(result).toBe(timeFixtures.expected.complexSec);
  });

  it('should handle large time values (24h)', () => {
    const result = convertSecondsToTimeString(timeFixtures.seconds.daysWorthSec);
    expect(result).toBe(timeFixtures.expected.daysSec);
  });

  it('should handle fractional input (should floor to integer seconds)', () => {
    const result = convertSecondsToTimeString(timeFixtures.seconds.fractionalInput);
    expect(result).toBe('1h 1m 1.5s'); // Function doesn't actually floor, it preserves fractional seconds
  });

  it('should handle negative values', () => {
    const result = convertSecondsToTimeString(-60);
    expect(result).toBe('0s'); // Current implementation doesn't handle negatives properly
  });

  it('should not include seconds when there are hours and minutes but no remainder seconds', () => {
    const result = convertSecondsToTimeString(3660); // 1h 1m 0s
    expect(result).toBe('1h 1m');
  });
});

describe('coloredTextCodeBlock', () => {
  beforeAll(() => {
    // Fixtures are already loaded above
    markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
    timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
    colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
  });
  it('should create red colored text block', () => {
    const input = colorFixtures.testText.simple;
    const result = coloredTextCodeBlock(input, 'red');
    expect(result).toBe(colorFixtures.expected.red.simple);
  });

  it('should create blue colored text block', () => {
    const input = colorFixtures.testText.simple;
    const result = coloredTextCodeBlock(input, 'blue');
    expect(result).toBe(colorFixtures.expected.blue.simple);
  });

  it('should create green colored text block', () => {
    const input = colorFixtures.testText.simple;
    const result = coloredTextCodeBlock(input, 'green');
    expect(result).toBe(colorFixtures.expected.green.simple);
  });

  it('should create yellow colored text block', () => {
    const input = colorFixtures.testText.simple;
    const result = coloredTextCodeBlock(input, 'yellow');
    expect(result).toBe(colorFixtures.expected.yellow.simple);
  });

  it('should handle empty text with red color', () => {
    const result = coloredTextCodeBlock('', 'red');
    expect(result).toBe(colorFixtures.expected.red.empty);
  });

  it('should handle empty text with blue color', () => {
    const result = coloredTextCodeBlock('', 'blue');
    expect(result).toBe(colorFixtures.expected.blue.empty);
  });

  it('should handle empty text with green color', () => {
    const result = coloredTextCodeBlock('', 'green');
    expect(result).toBe(colorFixtures.expected.green.empty);
  });

  it('should handle empty text with yellow color', () => {
    const result = coloredTextCodeBlock('', 'yellow');
    expect(result).toBe(colorFixtures.expected.yellow.empty);
  });

  it('should handle multiline text', () => {
    const input = colorFixtures.testText.multiline;
    const result = coloredTextCodeBlock(input, 'red');
    expect(result).toContain('Line 1\nLine 2\nLine 3');
    expect(result).toContain('```ansi');
    expect(result).toContain('\u001B[2;31m');
  });

  it('should handle special characters', () => {
    const input = colorFixtures.testText.withSpecialChars;
    const result = coloredTextCodeBlock(input, 'green');
    expect(result).toContain(input);
    expect(result).toContain('```ansi');
    expect(result).toContain('\u001B[2;36m');
  });

  it('should handle unicode characters', () => {
    const input = colorFixtures.testText.unicode;
    const result = coloredTextCodeBlock(input, 'yellow');
    expect(result).toContain(input);
    expect(result).toContain('```ansi');
    expect(result).toContain('\u001B[2;33m');
  });

  it('should handle code-like text', () => {
    const input = colorFixtures.testText.code;
    const result = coloredTextCodeBlock(input, 'blue');
    expect(result).toContain(input);
    expect(result).toContain('```ansi');
    expect(result).toContain('\u001B[2;32m\u001B[2;36m\u001B[2;34m');
  });

  it('should return original text for invalid color', () => {
    const input = 'test text';
    // TypeScript should prevent this, but testing runtime behavior
    const result = coloredTextCodeBlock(input, 'invalid' as any);
    expect(result).toBe(input);
  });
});

describe('Edge cases and error handling', () => {
  beforeAll(() => {
    // Fixtures are already loaded above
    markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
    timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
    colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
  });
  it('should handle malformed markup gracefully', () => {
    const input = '<b>unclosed bold <i>nested italic</b> text</i>';
    const result = cleanText(input);
    expect(result).toContain('**');
    expect(result).toContain('*');
  });

  it('should handle deeply nested markup', () => {
    const input = '<b><i><color=red><a href=showinfo:587>Deep Nesting</a></color></i></b>';
    const result = cleanText(input);
    expect(result).toBe('***[Deep Nesting](https://everef.net/types/587)***');
  });

  it('should handle very large time values', () => {
    const largeValue = 1000 * 60 * 60 * 24 * 365; // 1 year in ms
    const result = convertMillisecondsToTimeString(largeValue);
    expect(result).toContain('h');
    expect(result.length).toBeGreaterThan(0);
  });

  it('should handle very small time values', () => {
    const result = convertMillisecondsToTimeString(1);
    expect(result).toBe('0.0s');
  });

  it('should handle text with no markup', () => {
    const input = 'Plain text with no markup';
    const result = cleanText(input);
    expect(result).toBe(input);
  });

  it('should handle only whitespace in markup', () => {
    const input = '<b>   </b>';
    const result = cleanText(input);
    expect(result).toBe('**   **');
  });

  it('should handle EVE links with very large IDs', () => {
    const input = '<a href=showinfo:999999999>Large ID Item</a>';
    const result = cleanText(input);
    expect(result).toBe('[Large ID Item](https://everef.net/types/999999999)');
  });

  it('should handle color tags with various hex formats', () => {
    const testCases = [
      { input: '<color=0xABC123>hex with 0x</color>', expected: 'hex with 0x' },
      { input: '<color=ABC123>hex without 0x</color>', expected: 'hex without 0x' },
      { input: '<color=0xABC12345>8-digit hex</color>', expected: '8-digit hex' },
    ];

    testCases.forEach(({ input, expected }) => {
      const result = cleanText(input);
      expect(result).toBe(expected);
    });
  });
});
