import '@testing-library/jest-dom'
import { axe, toHaveNoViolations } from 'jest-axe'
import { vi } from 'vitest'
import {
  createElementTest,
  BaseTestConfig,
  setupConsoleMocking,
  restoreConsoleMocking,
} from '../../tests/test-framework'
import { CustomElementFor } from '../../tests/component-registry'
import './icon'
import { PktIcon } from './icon'

expect.extend(toHaveNoViolations)

export interface IconTestConfig extends BaseTestConfig {
  name?: string
  path?: string
}

// Use shared framework
export const createIconTest = async (config: IconTestConfig = {}) => {
  const { container, element } = await createElementTest<
    CustomElementFor<'pkt-icon'>,
    IconTestConfig
  >('pkt-icon', config)

  return {
    container,
    icon: element,
  }
}

// Cleanup after each test
afterEach(() => {
  document.body.innerHTML = ''
  // Clean up sessionStorage after tests
  sessionStorage.clear()
  // Reset global variables
  delete (window as any).pktFetch
  delete (window as any).pktIconPath
  // Restore console mocking
  restoreConsoleMocking()
})

// Mock fetch for icon loading
const mockFetch = vi.fn()
const mockSvgContent =
  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="test-path"></path></svg>'

beforeEach(() => {
  // Setup console mocking to suppress error logs during tests
  setupConsoleMocking()

  // Setup default mocks
  mockFetch.mockResolvedValue({
    ok: true,
    text: () => Promise.resolve(mockSvgContent),
  })
  window.pktFetch = mockFetch
  window.pktIconPath = 'https://test-cdn.example.com/icons/'
})

describe('PktIcon', () => {
  describe('Rendering and basic functionality', () => {
    test('renders without errors', async () => {
      const { icon } = await createIconTest()

      expect(icon).toBeInTheDocument()
      await icon.updateComplete
      expect(icon.classList.contains('pkt-icon')).toBe(true)
    })

    test('renders with default structure', async () => {
      const { icon } = await createIconTest({
        name: 'arrow-right',
      })
      await icon.updateComplete

      expect(icon).toBeInTheDocument()
      expect(icon.classList.contains('pkt-icon')).toBe(true)
    })

    test('renders nothing when no name is provided', async () => {
      const { icon } = await createIconTest()
      await icon.updateComplete

      // Should render nothing meaningful when no name is provided (only Lit template comments)
      expect(icon.innerHTML).not.toContain('<svg')
      expect(icon.name).toBe('')
    })
  })

  describe('Properties and attributes', () => {
    test('applies default properties correctly', async () => {
      const { icon } = await createIconTest()
      await icon.updateComplete

      expect(icon.name).toBe('')
      expect(icon.path).toBe('https://test-cdn.example.com/icons/')
    })

    test('sets name property correctly', async () => {
      const { icon } = await createIconTest({
        name: 'arrow-right',
      })
      await icon.updateComplete

      expect(icon.name).toBe('arrow-right')
      expect(icon.getAttribute('name')).toBe('arrow-right')
    })

    test('sets path property correctly', async () => {
      const customPath = 'https://custom-cdn.example.com/icons/'
      const { icon } = await createIconTest({
        path: customPath,
        name: 'arrow-right',
      })
      await icon.updateComplete

      expect(icon.path).toBe(customPath)
    })

    test('uses global pktIconPath when path not specified', async () => {
      window.pktIconPath = 'https://global-cdn.example.com/icons/'
      const { icon } = await createIconTest({
        name: 'arrow-right',
      })
      await icon.updateComplete

      expect(icon.path).toBe('https://global-cdn.example.com/icons/')
    })
  })

  describe('Icon loading functionality', () => {
    test('fetches icon from CDN when name is provided', async () => {
      const { icon } = await createIconTest({
        name: 'arrow-right',
      })
      await icon.updateComplete

      // Allow some time for async icon loading
      await new Promise((resolve) => setTimeout(resolve, 0))

      expect(mockFetch).toHaveBeenCalledWith('https://test-cdn.example.com/icons/arrow-right.svg')
    })

    test('caches loaded icons in sessionStorage', async () => {
      const { icon } = await createIconTest({
        name: 'arrow-right',
      })
      await icon.updateComplete

      // Wait for icon to load
      await new Promise((resolve) => setTimeout(resolve, 0))

      const cachedIcon = sessionStorage.getItem('https://test-cdn.example.com/icons/arrow-right.svg')
      expect(cachedIcon).toBe(mockSvgContent)
    })

    test('uses cached icon when available', async () => {
      // Pre-populate cache
      sessionStorage.setItem('https://test-cdn.example.com/icons/cached-icon.svg', mockSvgContent)

      const { icon } = await createIconTest({
        name: 'cached-icon',
      })
      await icon.updateComplete

      // Should not fetch since it's cached
      expect(mockFetch).not.toHaveBeenCalledWith(
        'https://test-cdn.example.com/icons/cached-icon.svg',
      )
    })

    test('handles fetch errors gracefully', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        text: () => Promise.resolve(''),
      })

      const { icon } = await createIconTest({
        name: 'missing-icon',
      })
      await icon.updateComplete

      // Should log error and use error SVG
      expect(mockFetch).toHaveBeenCalledWith('https://test-cdn.example.com/icons/missing-icon.svg')
    })
  })

  describe('Dynamic updates', () => {
    test('updates icon when name changes', async () => {
      const { icon } = await createIconTest({
        name: 'arrow-right',
      })
      await icon.updateComplete

      // Change the name
      icon.name = 'arrow-left'
      await icon.updateComplete

      expect(icon.name).toBe('arrow-left')
      expect(icon.getAttribute('name')).toBe('arrow-left')
    })

    test('updates icon when path changes', async () => {
      const { icon } = await createIconTest({
        name: 'arrow-right',
      })
      await icon.updateComplete

      const newPath = 'https://new-cdn.example.com/icons/'
      icon.path = newPath
      await icon.updateComplete

      expect(icon.path).toBe(newPath)
    })

    test('re-fetches icon when path changes', async () => {
      const { icon } = await createIconTest({
        name: 'arrow-right',
      })
      await icon.updateComplete

      // Allow initial load to complete
      await new Promise((resolve) => setTimeout(resolve, 0))

      mockFetch.mockClear()
      // Setup mock again for the new path
      mockFetch.mockResolvedValue({
        ok: true,
        text: () => Promise.resolve(mockSvgContent),
      })

      const newPath = 'https://new-cdn.example.com/icons/'

      // Try setting attribute to trigger attributeChangedCallback
      icon.setAttribute('path', newPath)
      await icon.updateComplete

      // Allow some time for async icon loading
      await new Promise((resolve) => setTimeout(resolve, 0))

      expect(mockFetch).toHaveBeenCalledWith('https://new-cdn.example.com/icons/arrow-right.svg')
    })
  })

  describe('CSS classes and styling', () => {
    test('applies pkt-icon class', async () => {
      const { icon } = await createIconTest({
        name: 'arrow-right',
      })
      await icon.updateComplete

      expect(icon.classList.contains('pkt-icon')).toBe(true)
    })

    test('maintains pkt-icon class after updates', async () => {
      const { icon } = await createIconTest({
        name: 'arrow-right',
      })
      await icon.updateComplete

      icon.name = 'arrow-left'
      await icon.updateComplete

      expect(icon.classList.contains('pkt-icon')).toBe(true)
    })
  })

  describe('Global configuration', () => {
    test('uses custom pktFetch function when provided', async () => {
      const customFetch = vi.fn().mockResolvedValue({
        ok: true,
        text: () => Promise.resolve('<svg>custom</svg>'),
      })

      window.pktFetch = customFetch

      const { icon } = await createIconTest({
        name: 'custom-icon',
      })
      await icon.updateComplete

      // Allow some time for async icon loading
      await new Promise((resolve) => setTimeout(resolve, 0))

      expect(customFetch).toHaveBeenCalledWith('https://test-cdn.example.com/icons/custom-icon.svg')
    })

    test('falls back to error SVG when pktFetch is not available', async () => {
      delete (window as any).pktFetch

      const { icon } = await createIconTest({
        name: 'fallback-icon',
      })
      await icon.updateComplete

      // Allow some time for async icon loading
      await new Promise((resolve) => setTimeout(resolve, 0))

      // Should render error SVG in light DOM when fetch is not available
      expect(icon.innerHTML).toContain('viewBox="0 0 32 32"')
    })
  })

  describe('Accessibility', () => {
    test('basic icon is accessible', async () => {
      const { container } = await createIconTest({
        name: 'arrow-right',
      })
      await new Promise((resolve) => setTimeout(resolve, 0))

      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('icon with custom path is accessible', async () => {
      const { container } = await createIconTest({
        name: 'arrow-right',
        path: 'https://custom-cdn.example.com/icons/',
      })
      await new Promise((resolve) => setTimeout(resolve, 0))

      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })
  })

  describe('Integration scenarios', () => {
    test('works with multiple icons simultaneously', async () => {
      const container = document.createElement('div')
      container.innerHTML = `
        <pkt-icon name="arrow-right"></pkt-icon>
        <pkt-icon name="arrow-left"></pkt-icon>
        <pkt-icon name="close"></pkt-icon>
      `
      document.body.appendChild(container)

      // Wait for elements to be defined
      await customElements.whenDefined('pkt-icon')

      const icons = container.querySelectorAll('pkt-icon')
      expect(icons).toHaveLength(3)

      for (const icon of icons) {
        await (icon as PktIcon).updateComplete
        expect(icon.classList.contains('pkt-icon')).toBe(true)
      }
    })

    test('handles rapid name changes correctly', async () => {
      const { icon } = await createIconTest({
        name: 'arrow-right',
      })
      await icon.updateComplete

      // Rapidly change names
      icon.name = 'arrow-left'
      icon.name = 'close'
      icon.name = 'menu'
      await icon.updateComplete

      expect(icon.name).toBe('menu')
      expect(icon.getAttribute('name')).toBe('menu')
    })
  })
})
