import '@testing-library/jest-dom'

import { render, waitFor } from '@testing-library/react'
import { axe, toHaveNoViolations } from 'jest-axe'
import type { THeaderFooterApi } from 'shared-types'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'

import { PktHeaderMenu } from './HeaderMenu'

expect.extend(toHaveNoViolations)

const LOCAL_DATA: THeaderFooterApi = {
  'nb-NO': {
    megamenu: {
      buttons: [{ text: 'Min side', url: 'https://www.oslo.kommune.no/min-side/' }],
      services: {
        title: 'Tjenester og tilbud',
        links: [
          { icon: '24h', text: 'Døgnåpne tjenester', url: 'https://example.test/24h' },
          { icon: 'fire-emblem', text: 'Brannvern', url: 'https://example.test/brannvern' },
          { icon: 'unknown-icon', text: 'Ukjent', url: 'https://example.test/ukjent' },
        ],
      },
      sections: [
        {
          title: 'Oslo vokser',
          links: [{ text: 'Byutvikling', url: 'https://example.test/byutvikling' }],
        },
        {
          title: 'Politikk og innsyn',
          links: [{ text: 'Politikk', url: 'https://example.test/politikk' }],
        },
      ],
      links: [
        { text: 'Kontakt', url: 'https://example.test/kontakt' },
        { text: 'English', url: 'https://example.test/english' },
      ],
      some: [
        { text: 'Facebook', url: 'https://www.facebook.com/Oslo/' },
        { text: 'LinkedIn', url: 'https://www.linkedin.com/company/oslo-kommune' },
      ],
    },
    footer: { sections: [], links: [], some: [] },
    i18n: { menu: 'Meny', search: 'Søk', navAriaLabel: 'Oslo kommune hovedmeny' },
  },
  'en-GB': {
    megamenu: {
      services: {
        title: 'Services and information',
        links: [{ icon: '24h', text: '24h', url: 'https://example.test/en/24h' }],
      },
      sections: [],
      links: [],
      some: [],
    },
    footer: { sections: [], links: [], some: [] },
    i18n: { menu: 'Menu', search: 'Search', navAriaLabel: 'Oslo kommune main menu' },
  },
}

afterEach(() => {
  vi.restoreAllMocks()
})

describe('PktHeaderMenu rendering', () => {
  test('renders services, sections and footer when given pre-fetched data', () => {
    // jsdom reports width 0 (mobile/accordion layout); force desktop.
    const { container } = render(<PktHeaderMenu data={LOCAL_DATA} mobileBreakpoint={0} open />)
    expect(container.querySelector('.pkt-header-menu__services-title')?.textContent).toContain('Tjenester og tilbud')
    expect(container.querySelectorAll('.pkt-header-menu__service')).toHaveLength(3)
    expect(container.querySelectorAll('.pkt-header-menu__section')).toHaveLength(2)
    expect(container.querySelectorAll('.pkt-header-menu__footer-link')).toHaveLength(2)
  })

  test('does not fetch when data prop is supplied', () => {
    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('should not run'))
    render(<PktHeaderMenu data={LOCAL_DATA} open />)
    expect(fetchSpy).not.toHaveBeenCalled()
  })

  test('maps ODS icon names on service links', async () => {
    const { container } = render(<PktHeaderMenu data={LOCAL_DATA} open />)
    const icons = container.querySelectorAll<HTMLElement>('.pkt-header-menu__service-icon')
    await waitFor(() => expect(icons[0]).toHaveAttribute('name', '24h'))
    expect(icons[1]).toHaveAttribute('name', 'shield-fire')
    // Unmapped passes through
    expect(icons[2]).toHaveAttribute('name', 'unknown-icon')
  })

  test('reflects the open prop to a host class', () => {
    const { container, rerender } = render(<PktHeaderMenu data={LOCAL_DATA} open />)
    const root = container.firstChild as HTMLElement
    expect(root).toHaveClass('pkt-header-menu')
    expect(root).toHaveClass('pkt-header-menu--open')
    rerender(<PktHeaderMenu data={LOCAL_DATA} open={false} />)
    expect(root).not.toHaveClass('pkt-header-menu--open')
  })

  test('uses the locale-specific nav aria-label', () => {
    const { container } = render(<PktHeaderMenu data={LOCAL_DATA} locale="nb-NO" open />)
    expect(container.querySelector('nav')?.getAttribute('aria-label')).toBe('Oslo kommune hovedmeny')
  })

  test('switches content when locale changes', () => {
    const { container, rerender } = render(<PktHeaderMenu data={LOCAL_DATA} locale="nb-NO" mobileBreakpoint={0} open />)
    expect(container.querySelector('.pkt-header-menu__services-title')?.textContent).toContain('Tjenester og tilbud')
    rerender(<PktHeaderMenu data={LOCAL_DATA} locale="en-GB" mobileBreakpoint={0} open />)
    expect(container.querySelector('.pkt-header-menu__services-title')?.textContent).toContain(
      'Services and information',
    )
    expect(container.querySelector('nav')?.getAttribute('aria-label')).toBe('Oslo kommune main menu')
  })

  test('renders social links with derived aria-labels and icons', async () => {
    const { container } = render(<PktHeaderMenu data={LOCAL_DATA} open />)
    const social = container.querySelectorAll<HTMLElement>('.pkt-header-menu__social-link')
    expect(social).toHaveLength(2)
    expect(social[0]).toHaveAttribute('aria-label', 'Facebook')
    expect(social[1]).toHaveAttribute('aria-label', 'LinkedIn')
    await waitFor(() => expect(social[0].querySelector('.pkt-icon')).toHaveAttribute('name', 'facebook'))
    expect(social[1].querySelector('.pkt-icon')).toHaveAttribute('name', 'linkedin')
  })

  test('calls onDataLoaded with the supplied payload', async () => {
    const onDataLoaded = vi.fn()
    render(<PktHeaderMenu data={LOCAL_DATA} open onDataLoaded={onDataLoaded} />)
    await waitFor(() => expect(onDataLoaded).toHaveBeenCalledWith(LOCAL_DATA))
  })
})

describe('PktHeaderMenu fetching', () => {
  beforeEach(() => {
    vi.spyOn(globalThis, 'fetch').mockImplementation(() =>
      Promise.resolve(
        new Response(JSON.stringify(LOCAL_DATA), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        }),
      ),
    )
  })

  test('fetches the default URL on mount when no data is supplied', async () => {
    const { container } = render(<PktHeaderMenu mobileBreakpoint={0} open />)
    await waitFor(() => expect(globalThis.fetch).toHaveBeenCalled())
    expect(globalThis.fetch).toHaveBeenCalledWith('https://cdn.web.oslo.kommune.no/header-footer/header-footer.json')
    await waitFor(() =>
      expect(container.querySelector('.pkt-header-menu__services-title')?.textContent).toContain('Tjenester og tilbud'),
    )
  })

  test('honors a custom data-url', async () => {
    render(<PktHeaderMenu dataUrl="https://custom.test/payload.json" open />)
    await waitFor(() => expect(globalThis.fetch).toHaveBeenCalledWith('https://custom.test/payload.json'))
  })

  test('calls onDataError when the fetch fails', async () => {
    vi.restoreAllMocks()
    vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response('boom', { status: 500 }) as unknown as Response)
    const onDataError = vi.fn()
    const { container } = render(<PktHeaderMenu open onDataError={onDataError} />)
    await waitFor(() => expect(onDataError).toHaveBeenCalledTimes(1))
    expect((onDataError.mock.calls[0][0] as Error).message).toMatch(/HTTP 500/)
    expect(container.querySelector('.pkt-header-menu__error')).not.toBeNull()
  })
})

describe('PktHeaderMenu accessibility', () => {
  test('has no axe violations with the local-data payload', async () => {
    const { container } = render(<PktHeaderMenu data={LOCAL_DATA} open />)
    const results = await axe(container)
    expect(results).toHaveNoViolations()
  })
})
