import '@testing-library/jest-dom'

import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { axe, toHaveNoViolations } from 'jest-axe'
import { afterEach, beforeEach, vi } from 'vitest'

import { PktConsent } from './Consent'

expect.extend(toHaveNoViolations)

type CapturedHandler = ((consent: { value: string | { items: unknown[] } }) => void) | undefined
let capturedHandler: CapturedHandler

const installCookieBannerStub = ({ hasConsent = false }: { hasConsent?: boolean } = {}) => {
  Object.defineProperty(window, 'cookieBanner', {
    value: {
      cookieConsent: {
        validateConsentCookie: vi.fn().mockResolvedValue(hasConsent),
        getConsentCookie: vi.fn().mockReturnValue('{"items":[]}'),
      },
      openCookieModal: vi.fn(),
    },
    writable: true,
    configurable: true,
  })
  capturedHandler = undefined
  Object.defineProperty(window, '__cookieEvents', {
    value: {
      on: vi.fn((_event: string, handler: CapturedHandler) => {
        capturedHandler = handler
      }),
      off: vi.fn(),
    },
    writable: true,
    configurable: true,
  })
}

const preInjectScriptMarker = () => {
  // The component's loadConsentScript() resolves immediately if the marker
  // script is already in the DOM. Inject it up-front so the test doesn't
  // depend on a network fetch that jsdom can't fulfil.
  if (!document.querySelector('#oslo-consent-script')) {
    const marker = document.createElement('script')
    marker.id = 'oslo-consent-script'
    document.head.appendChild(marker)
  }
}

describe('PktConsent', () => {
  beforeEach(() => {
    preInjectScriptMarker()
    installCookieBannerStub()
  })

  afterEach(() => {
    document.querySelector('#oslo-consent-script')?.remove()
    document.querySelector('#oslo-consent-styles')?.remove()
    delete (window as { cookieBanner?: unknown }).cookieBanner
    delete (window as { __cookieEvents?: unknown }).__cookieEvents
    capturedHandler = undefined
  })

  describe('Trigger rendering', () => {
    test('renders the default button trigger with the bokmål settings label', () => {
      render(<PktConsent />)

      expect(screen.getByRole('button', { name: 'Innstillinger for informasjonskapsler' })).toBeInTheDocument()
    })

    test('uses the English settings label when i18nLanguage is "en"', () => {
      render(<PktConsent i18nLanguage="en" />)

      expect(screen.getByRole('button', { name: 'Go to settings' })).toBeInTheDocument()
    })

    test('renders a custom triggerText when provided', () => {
      render(<PktConsent triggerText="Endre samtykke" />)

      expect(screen.getByRole('button', { name: 'Endre samtykke' })).toBeInTheDocument()
    })

    test('renders a link trigger when triggerType="link"', () => {
      const { container } = render(<PktConsent triggerType="link" triggerText="Innstillinger" />)

      const link = container.querySelector('a.pkt-link')
      expect(link).toBeInTheDocument()
      expect(link).toHaveAttribute('href', '#')
      expect(link).toHaveTextContent('Innstillinger')
    })

    test('renders a plain footer link when triggerType="footerlink"', () => {
      const { container } = render(<PktConsent triggerType="footerlink" triggerText="Innstillinger" />)

      const link = container.querySelector('a.pkt-footer__link')
      expect(link).toBeInTheDocument()
      expect(link).toHaveAttribute('aria-haspopup', 'dialog')
      // Ny footer-stil: ren understreket lenke uten ikon.
      expect(link?.querySelector('.pkt-icon')).toBeNull()
    })

    test('renders an icon-only button when triggerType="icon"', () => {
      const { container } = render(<PktConsent triggerType="icon" triggerText="Cookies" />)

      const button = container.querySelector('button')
      expect(button).toHaveClass('pkt-btn--icon-only')
      expect(button).toHaveClass('pkt-btn--tertiary')
    })
  })

  describe('Click behaviour', () => {
    test('opens the cookie modal on button click', () => {
      vi.useFakeTimers()
      try {
        render(<PktConsent />)
        fireEvent.click(screen.getByRole('button'))
        vi.runAllTimers()
        expect(window.cookieBanner?.openCookieModal).toHaveBeenCalled()
      } finally {
        vi.useRealTimers()
      }
    })

    test('opens the cookie modal on link click', () => {
      vi.useFakeTimers()
      try {
        const { container } = render(<PktConsent triggerType="link" />)
        const link = container.querySelector('a.pkt-link') as HTMLAnchorElement
        fireEvent.click(link)
        vi.runAllTimers()
        expect(window.cookieBanner?.openCookieModal).toHaveBeenCalled()
      } finally {
        vi.useRealTimers()
      }
    })
  })

  describe('Side effects', () => {
    test('sets the cookie banner globals from props', async () => {
      render(
        <PktConsent
          devMode
          googleAnalyticsId="GA-123"
          hotjarId="HJ-456"
          cookieDomain=".example.com"
          cookieSecure="true"
          cookieExpiryDays="90"
        />,
      )

      await waitFor(() => {
        expect(window.cookieBanner_googleAnalyticsId).toBe('GA-123')
      })
      expect(window.cookieBanner_hotjarId).toBe('HJ-456')
      expect(window.cookieBanner_cookieDomain).toBe('.example.com')
      expect(window.cookieBanner_cookieSecure).toBe('true')
      expect(window.cookieBanner_cookieExpiryDays).toBe('90')
      expect(window.cookieBanner_devMode).toBe(true)
    })
  })

  describe('onToggleConsent', () => {
    test('invokes onToggleConsent with a CustomEvent carrying parsed consent details', async () => {
      installCookieBannerStub({ hasConsent: true })
      const onToggle = vi.fn()

      render(<PktConsent onToggleConsent={onToggle} />)

      // First emit comes from the initial consent-cookie read; wait for it,
      // then clear so we can assert specifically on the handler-driven call.
      await waitFor(() => expect(capturedHandler).toBeDefined())
      await waitFor(() => expect(onToggle).toHaveBeenCalled())
      onToggle.mockClear()

      capturedHandler!({
        value: JSON.stringify({
          items: [
            { name: 'statistics', consent: true },
            { name: 'survey', consent: false },
          ],
        }),
      })

      expect(onToggle).toHaveBeenCalledTimes(1)
      const event = onToggle.mock.calls[0][0] as CustomEvent<Record<string, boolean>>
      expect(event).toBeInstanceOf(CustomEvent)
      expect(event.type).toBe('toggle-consent')
      expect(event.detail).toEqual({ statistics: true, survey: false })
    })
  })

  describe('Accessibility', () => {
    test('button trigger has no axe violations', async () => {
      const { container } = render(<PktConsent triggerText="Cookie settings" />)
      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('link trigger has no axe violations', async () => {
      const { container } = render(<PktConsent triggerType="link" triggerText="Cookie settings" />)
      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('footer link trigger has no axe violations', async () => {
      const { container } = render(<PktConsent triggerType="footerlink" triggerText="Cookie settings" />)
      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })
  })
})
