import { describe, it, expect } from 'vitest'
import { render } from '@testing-library/react'
import { MarkdownContent } from './markdown-content'

describe('<MarkdownContent>', () => {
  it('renders markdown with the safe defaults (no html, no images)', () => {
    const { container } = render(
      <MarkdownContent
        content={
          '# Title\n\n![alt](https://example.com/img.png)\n\n<span>raw</span>'
        }
      />,
    )
    // Image must NOT render — disallowedElements blocks it.
    expect(container.querySelector('img')).toBeNull()
    // Raw HTML escapes to text — span never gets parsed as an element.
    expect(container.querySelector('span')).toBeNull()
    // Heading still renders normally.
    expect(container.textContent).toContain('Title')
  })

  it('renders images when allowImages=true', () => {
    const { container } = render(
      <MarkdownContent
        content={'![alt](https://example.com/img.png)'}
        allowImages
      />,
    )
    const img = container.querySelector('img')
    expect(img).not.toBeNull()
    expect(img?.getAttribute('alt')).toBe('alt')
  })

  it('parses GFM tables only when gfm=true', () => {
    const table = '| a | b |\n| --- | --- |\n| 1 | 2 |'
    const { container: plain } = render(<MarkdownContent content={table} />)
    // Without the flag, GFM syntax stays CommonMark text — no table element.
    expect(plain.querySelector('table')).toBeNull()
    const { container } = render(<MarkdownContent content={table} gfm />)
    expect(container.querySelector('table')).not.toBeNull()
    expect(container.querySelector('td')?.textContent).toBe('1')
  })

  it('parses raw HTML when allowHtml=true', () => {
    const { container } = render(
      <MarkdownContent content={'<span>raw <em>html</em></span>'} allowHtml />,
    )
    // Both inline tags survive sanitization; the text content is preserved.
    expect(container.querySelector('span')).not.toBeNull()
    expect(container.querySelector('em')?.textContent).toBe('html')
    expect(container.textContent).toContain('raw html')
  })

  describe('XSS hardening', () => {
    it('drops <script> tags when allowHtml=true', () => {
      const { container } = render(
        <MarkdownContent
          content={'<script>window.__xss=true</script>safe'}
          allowHtml
          allowImages
        />,
      )
      expect(container.querySelector('script')).toBeNull()
      expect(container.textContent).toContain('safe')
    })

    it('strips event handlers from <img>', () => {
      const { container } = render(
        <MarkdownContent
          content={'<img src="https://example.com/x.png" onerror="alert(1)">'}
          allowHtml
          allowImages
        />,
      )
      const img = container.querySelector('img')
      expect(img).not.toBeNull()
      expect(img?.getAttribute('onerror')).toBeNull()
    })

    it('drops <iframe> with srcdoc', () => {
      const { container } = render(
        <MarkdownContent
          content={'<iframe srcdoc="<script>alert(1)</script>"></iframe>'}
          allowHtml
          allowImages
        />,
      )
      expect(container.querySelector('iframe')).toBeNull()
    })

    it('drops <svg> with onload', () => {
      const { container } = render(
        <MarkdownContent
          content={'<svg onload="alert(1)"><circle cx="5" cy="5" r="4"/></svg>'}
          allowHtml
          allowImages
        />,
      )
      const svg = container.querySelector('svg')
      // defaultSchema doesn't allowlist <svg>, so the element should be gone.
      // Even if a future schema variant allows it, `onload` must not survive.
      expect(svg?.getAttribute('onload') ?? null).toBeNull()
    })

    it('drops <base> tags that could rewrite relative URLs', () => {
      const { container } = render(
        <MarkdownContent
          content={'<base href="https://attacker.example/">link'}
          allowHtml
        />,
      )
      expect(container.querySelector('base')).toBeNull()
    })

    it('strips href for javascript: URLs in markdown links', () => {
      const { container } = render(
        <MarkdownContent content={'[click](javascript:alert(1))'} />,
      )
      const a = container.querySelector('a')
      // Either the anchor renders without `href` or with empty `href` — both
      // are safe (no navigation). Concretely we assert no `javascript:` value.
      expect(a?.getAttribute('href') ?? '').not.toMatch(/^javascript:/i)
    })

    it('strips href for data: URLs in markdown links (Note path)', () => {
      const { container } = render(
        <MarkdownContent
          content={'[click](data:text/html,<script>alert(1)</script>)'}
        />,
      )
      const a = container.querySelector('a')
      expect(a?.getAttribute('href') ?? '').not.toMatch(/^data:/i)
    })

    it('strips data: URLs from <img src> on the rich path', () => {
      const { container } = render(
        <MarkdownContent
          content={'![alt](data:image/svg+xml,<svg onload=alert(1)/>)'}
          allowImages
        />,
      )
      const img = container.querySelector('img')
      expect(img?.getAttribute('src') ?? '').not.toMatch(/^data:/i)
    })

    it('preserves safe URLs (https, mailto, relative)', () => {
      const { container } = render(
        <MarkdownContent
          content={
            '[ext](https://example.com) [mail](mailto:a@b.co) [rel](./page) [hash](#x)'
          }
        />,
      )
      const links = Array.from(container.querySelectorAll('a')).map((a) =>
        a.getAttribute('href'),
      )
      expect(links).toEqual([
        'https://example.com',
        'mailto:a@b.co',
        './page',
        '#x',
      ])
    })
  })
})
