import { describe, it, expect } from 'vitest';
import {
  formatIssueBody,
  generateIssueTitle,
  determineLabels,
} from '../src/formatter.js';

describe('formatIssueBody', () => {
  const baseData = {
    topic: 'Testing Best Practices',
    insights: {
      sources: [
        {
          title: 'Testing Guide',
          url: 'https://example.com/guide',
          snippet: 'Comprehensive testing guide',
          domain: 'example.com',
          relevance: 0.95,
        },
      ],
      keyPoints: [
        {
          text: 'You should always write unit tests',
          source: 'https://example.com/guide',
          sourceTitle: 'Testing Guide',
        },
      ],
      topics: ['testing', 'javascript', 'automation'],
      detectedPriority: 'normal',
      actionable: false,
    },
    sources: [
      {
        title: 'Testing Guide',
        url: 'https://example.com/guide',
      },
    ],
    generatedAt: '2024-01-15T12:00:00Z',
    priority: 'normal',
  };

  describe('basic formatting', () => {
    it('should include topic in header', () => {
      const body = formatIssueBody(baseData);

      expect(body).toContain('# Research: Testing Best Practices');
    });

    it('should include generation date', () => {
      const body = formatIssueBody(baseData);

      expect(body).toContain('1/15/2024'); // Date format depends on locale
      expect(body).toContain('Research compiled from');
    });

    it('should include source count', () => {
      const body = formatIssueBody(baseData);

      expect(body).toContain('Research compiled from 1 sources');
    });

    it('should include plugin attribution', () => {
      const body = formatIssueBody(baseData);

      expect(body).toContain('Generated by');
      expect(body).toContain('web-to-github-issue');
      expect(body).toContain('claudecodeplugins.io');
    });

    it('should include research date at bottom', () => {
      const body = formatIssueBody(baseData);

      expect(body).toContain('Research date:');
    });
  });

  describe('priority handling', () => {
    it('should display urgent priority', () => {
      const urgentData = {
        ...baseData,
        priority: 'urgent',
      };

      const body = formatIssueBody(urgentData);

      expect(body).toContain('**Priority:** URGENT');
    });

    it('should display high priority', () => {
      const highData = {
        ...baseData,
        priority: 'high',
      };

      const body = formatIssueBody(highData);

      expect(body).toContain('**Priority:** HIGH');
    });

    it('should not display normal priority', () => {
      const normalData = {
        ...baseData,
        priority: 'normal',
      };

      const body = formatIssueBody(normalData);

      expect(body).not.toContain('**Priority:**');
    });

    it('should not display priority when not set', () => {
      const noPriorityData = {
        ...baseData,
        priority: undefined,
      };

      const body = formatIssueBody(noPriorityData);

      expect(body).not.toContain('**Priority:**');
    });
  });

  describe('key findings section', () => {
    it('should display key findings with sources', () => {
      const body = formatIssueBody(baseData);

      expect(body).toContain('## Key Findings');
      expect(body).toContain('You should always write unit tests');
      expect(body).toContain('[Testing Guide](https://example.com/guide)');
    });

    it('should handle multiple key points', () => {
      const multiPointData = {
        ...baseData,
        insights: {
          ...baseData.insights,
          keyPoints: [
            {
              text: 'First point',
              source: 'https://example.com/1',
              sourceTitle: 'Source 1',
            },
            {
              text: 'Second point',
              source: 'https://example.com/2',
              sourceTitle: 'Source 2',
            },
          ],
        },
      };

      const body = formatIssueBody(multiPointData);

      expect(body).toContain('First point');
      expect(body).toContain('Second point');
      expect(body).toContain('[Source 1](https://example.com/1)');
      expect(body).toContain('[Source 2](https://example.com/2)');
    });

    it('should omit key findings section when no key points', () => {
      const noPointsData = {
        ...baseData,
        insights: {
          ...baseData.insights,
          keyPoints: [],
        },
      };

      const body = formatIssueBody(noPointsData);

      expect(body).not.toContain('## Key Findings');
    });

    it('should handle missing key points array', () => {
      const noArrayData = {
        ...baseData,
        insights: {
          ...baseData.insights,
          keyPoints: undefined,
        },
      };

      const body = formatIssueBody(noArrayData);

      expect(body).not.toContain('## Key Findings');
    });
  });

  describe('topics section', () => {
    it('should display topics with code formatting', () => {
      const body = formatIssueBody(baseData);

      expect(body).toContain('## Related Topics');
      expect(body).toContain('`testing`');
      expect(body).toContain('`javascript`');
      expect(body).toContain('`automation`');
      expect(body).toContain('•'); // Bullet separator
    });

    it('should omit topics section when no topics', () => {
      const noTopicsData = {
        ...baseData,
        insights: {
          ...baseData.insights,
          topics: [],
        },
      };

      const body = formatIssueBody(noTopicsData);

      expect(body).not.toContain('## Related Topics');
    });

    it('should handle missing topics array', () => {
      const noArrayData = {
        ...baseData,
        insights: {
          ...baseData.insights,
          topics: undefined,
        },
      };

      const body = formatIssueBody(noArrayData);

      expect(body).not.toContain('## Related Topics');
    });

    it('should join topics with bullet separator', () => {
      const body = formatIssueBody(baseData);

      const topicsSection = body.split('## Related Topics')[1].split('##')[0];
      const bullets = (topicsSection.match(/•/g) || []).length;

      expect(bullets).toBe(2); // 3 topics = 2 separators
    });
  });

  describe('sources section', () => {
    it('should display sources with titles and URLs', () => {
      const body = formatIssueBody(baseData);

      expect(body).toContain('## Sources');
      expect(body).toContain('### [Testing Guide](https://example.com/guide)');
      expect(body).toContain('Comprehensive testing guide');
      expect(body).toContain('*Source: example.com*');
    });

    it('should handle multiple sources', () => {
      const multiSourceData = {
        ...baseData,
        insights: {
          ...baseData.insights,
          sources: [
            {
              title: 'Guide 1',
              url: 'https://example.com/1',
              snippet: 'First guide',
              domain: 'example.com',
            },
            {
              title: 'Guide 2',
              url: 'https://example.com/2',
              snippet: 'Second guide',
              domain: 'other.com',
            },
          ],
        },
      };

      const body = formatIssueBody(multiSourceData);

      expect(body).toContain('[Guide 1](https://example.com/1)');
      expect(body).toContain('[Guide 2](https://example.com/2)');
      expect(body).toContain('First guide');
      expect(body).toContain('Second guide');
    });

    it('should handle sources without snippets', () => {
      const noSnippetData = {
        ...baseData,
        insights: {
          ...baseData.insights,
          sources: [
            {
              title: 'Guide',
              url: 'https://example.com',
              domain: 'example.com',
            },
          ],
        },
      };

      const body = formatIssueBody(noSnippetData);

      expect(body).toContain('[Guide](https://example.com)');
      expect(body).toContain('*Source: example.com*');
    });

    it('should separate sources with horizontal rules', () => {
      const multiSourceData = {
        ...baseData,
        insights: {
          ...baseData.insights,
          sources: [
            {
              title: 'Guide 1',
              url: 'https://example.com/1',
              snippet: 'First',
              domain: 'example.com',
            },
            {
              title: 'Guide 2',
              url: 'https://example.com/2',
              snippet: 'Second',
              domain: 'example.com',
            },
          ],
        },
      };

      const body = formatIssueBody(multiSourceData);

      const separators = (body.match(/---/g) || []).length;

      expect(separators).toBeGreaterThanOrEqual(2);
    });
  });

  describe('next steps section', () => {
    it('should include next steps when actionable', () => {
      const actionableData = {
        ...baseData,
        insights: {
          ...baseData.insights,
          actionable: true,
        },
      };

      const body = formatIssueBody(actionableData);

      expect(body).toContain('## Next Steps');
      expect(body).toContain('- [ ] Review findings');
      expect(body).toContain('- [ ] Create implementation plan');
      expect(body).toContain('- [ ] Assign to team member');
      expect(body).toContain('- [ ] Set timeline');
    });

    it('should omit next steps when not actionable', () => {
      const notActionableData = {
        ...baseData,
        insights: {
          ...baseData.insights,
          actionable: false,
        },
      };

      const body = formatIssueBody(notActionableData);

      expect(body).not.toContain('## Next Steps');
    });
  });

  describe('markdown validity', () => {
    it('should produce valid markdown structure', () => {
      const body = formatIssueBody(baseData);

      // Check for proper heading hierarchy
      expect(body).toMatch(/^# /m);
      expect(body).toMatch(/^## /m);
      expect(body).toMatch(/^### /m);

      // Check for proper list formatting
      expect(body).toMatch(/^- /m);

      // Check for proper link formatting
      expect(body).toMatch(/\[.+\]\(.+\)/);
    });

    it('should escape special markdown characters in user content', () => {
      const specialCharsData = {
        ...baseData,
        topic: 'Testing with *asterisks* and _underscores_',
      };

      const body = formatIssueBody(specialCharsData);

      expect(body).toContain('# Research: Testing with *asterisks* and _underscores_');
    });

    it('should handle URLs with special characters', () => {
      const specialUrlData = {
        ...baseData,
        insights: {
          ...baseData.insights,
          sources: [
            {
              title: 'Guide',
              url: 'https://example.com/page?param=value&other=123',
              snippet: 'Test',
              domain: 'example.com',
            },
          ],
        },
      };

      const body = formatIssueBody(specialUrlData);

      expect(body).toContain(
        '[Guide](https://example.com/page?param=value&other=123)'
      );
    });
  });

  describe('edge cases', () => {
    it('should handle empty insights gracefully', () => {
      const emptyInsightsData = {
        topic: 'Test Topic',
        insights: {
          sources: [],
          keyPoints: [],
          topics: [],
          detectedPriority: 'normal',
          actionable: false,
        },
        sources: [],
        generatedAt: '2024-01-15T12:00:00Z',
        priority: 'normal',
      };

      expect(() => formatIssueBody(emptyInsightsData)).not.toThrow();

      const body = formatIssueBody(emptyInsightsData);

      expect(body).toContain('# Research: Test Topic');
    });

    it('should handle very long topics', () => {
      const longTopicData = {
        ...baseData,
        topic: 'A'.repeat(200),
      };

      const body = formatIssueBody(longTopicData);

      expect(body).toContain('A'.repeat(200));
    });

    it('should handle special date formats', () => {
      const futureDateData = {
        ...baseData,
        generatedAt: '2025-12-31T23:59:59Z',
      };

      expect(() => formatIssueBody(futureDateData)).not.toThrow();
    });

    it('should handle missing generatedAt', () => {
      const noDateData = {
        ...baseData,
        generatedAt: undefined,
      };

      // Should not throw, will use Invalid Date
      expect(() => formatIssueBody(noDateData)).not.toThrow();
    });
  });
});

describe('generateIssueTitle', () => {
  it('should prefix "Implement:" for actionable content', () => {
    const insights = {
      actionable: true,
      detectedPriority: 'normal',
    };

    const title = generateIssueTitle('Testing Framework', insights);

    expect(title).toBe('Implement: Testing Framework');
  });

  it('should prefix "[URGENT]" for urgent priority', () => {
    const insights = {
      actionable: false,
      detectedPriority: 'urgent',
    };

    const title = generateIssueTitle('Security Issue', insights);

    expect(title).toBe('[URGENT] Research: Security Issue');
  });

  it('should prefer actionable over urgent', () => {
    const insights = {
      actionable: true,
      detectedPriority: 'urgent',
    };

    const title = generateIssueTitle('Critical Fix', insights);

    expect(title).toBe('Implement: Critical Fix');
  });

  it('should use "Research:" for normal content', () => {
    const insights = {
      actionable: false,
      detectedPriority: 'normal',
    };

    const title = generateIssueTitle('Documentation', insights);

    expect(title).toBe('Research: Documentation');
  });

  it('should handle empty topic', () => {
    const insights = {
      actionable: false,
      detectedPriority: 'normal',
    };

    const title = generateIssueTitle('', insights);

    expect(title).toBe('Research: ');
  });

  it('should handle long topics', () => {
    const insights = {
      actionable: false,
      detectedPriority: 'normal',
    };

    const longTopic = 'A'.repeat(100);
    const title = generateIssueTitle(longTopic, insights);

    expect(title).toBe(`Research: ${longTopic}`);
  });

  it('should handle special characters in topic', () => {
    const insights = {
      actionable: false,
      detectedPriority: 'normal',
    };

    const title = generateIssueTitle('Testing & Deployment', insights);

    expect(title).toBe('Research: Testing & Deployment');
  });
});

describe('determineLabels', () => {
  it('should add priority-high for urgent priority', () => {
    const insights = { actionable: false };
    const labels = determineLabels(['research'], 'urgent', insights);

    expect(labels).toContain('priority-high');
    expect(labels).toContain('research');
  });

  it('should add priority-high for high priority', () => {
    const insights = { actionable: false };
    const labels = determineLabels(['research'], 'high', insights);

    expect(labels).toContain('priority-high');
  });

  it('should not add priority-high for normal priority', () => {
    const insights = { actionable: false };
    const labels = determineLabels(['research'], 'normal', insights);

    expect(labels).not.toContain('priority-high');
  });

  it('should add actionable label when insights are actionable', () => {
    const insights = { actionable: true };
    const labels = determineLabels(['research'], 'normal', insights);

    expect(labels).toContain('actionable');
  });

  it('should not add actionable label when not actionable', () => {
    const insights = { actionable: false };
    const labels = determineLabels(['research'], 'normal', insights);

    expect(labels).not.toContain('actionable');
  });

  it('should combine all applicable labels', () => {
    const insights = { actionable: true };
    const labels = determineLabels(['research', 'documentation'], 'urgent', insights);

    expect(labels).toContain('research');
    expect(labels).toContain('documentation');
    expect(labels).toContain('priority-high');
    expect(labels).toContain('actionable');
  });

  it('should remove duplicate labels', () => {
    const insights = { actionable: true };
    const labels = determineLabels(
      ['research', 'actionable', 'priority-high'],
      'urgent',
      insights
    );

    const uniqueLabels = [...new Set(labels)];

    expect(labels.length).toBe(uniqueLabels.length);
  });

  it('should handle empty base labels', () => {
    const insights = { actionable: true };
    const labels = determineLabels([], 'urgent', insights);

    expect(labels).toContain('priority-high');
    expect(labels).toContain('actionable');
  });

  it('should preserve original base labels', () => {
    const insights = { actionable: false };
    const baseLabels = ['custom-label', 'another-label'];
    const labels = determineLabels(baseLabels, 'normal', insights);

    expect(labels).toContain('custom-label');
    expect(labels).toContain('another-label');
  });

  it('should handle undefined priority gracefully', () => {
    const insights = { actionable: false };
    const labels = determineLabels(['research'], undefined, insights);

    expect(labels).toEqual(['research']);
  });

  it('should handle null priority gracefully', () => {
    const insights = { actionable: false };
    const labels = determineLabels(['research'], null, insights);

    expect(labels).toEqual(['research']);
  });

  it('should not mutate input arrays', () => {
    const insights = { actionable: true };
    const baseLabels = ['research'];
    const originalLength = baseLabels.length;

    determineLabels(baseLabels, 'urgent', insights);

    expect(baseLabels.length).toBe(originalLength);
  });
});
