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

expect.extend(toHaveNoViolations)

export interface CardTestConfig extends BaseTestConfig {
  skin?: string
  layout?: string
  padding?: string
  borderOnHover?: boolean
  heading?: string
  subheading?: string
  headingLevel?: number
  href?: string
  linkText?: string
  image?: string
  imageAlt?: string
  tags?: string
  metaPrefix?: string
  metaLabel?: string
  metaDate?: string
}

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

  return {
    container,
    card: element,
  }
}

// Cleanup after each test
afterEach(() => {
  document.body.innerHTML = ''
})

// Global console.warn spy to suppress validation warnings in tests
let consoleWarnSpy: any
beforeEach(() => {
  consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
})

afterEach(() => {
  if (consoleWarnSpy) {
    consoleWarnSpy.mockRestore()
  }
})

describe('PktCard', () => {
  describe('Rendering and basic functionality', () => {
    test('renders without errors', async () => {
      const { card } = await createCardTest()

      expect(card).toBeInTheDocument()

      await card.updateComplete
      expect(card).toBeTruthy()

      const article = card.querySelector('article')
      expect(article).toBeInTheDocument()
      expect(article).toHaveClass('pkt-card')
    })

    test('renders content in slot', async () => {
      const { card } = await createCardTest({ content: '<p>Test content here</p>' })
      await card.updateComplete

      const content = card.querySelector('.pkt-card__content')
      expect(content).toBeInTheDocument()
      expect(content?.textContent).toContain('Test content here')
    })

    test('renders basic structure correctly', async () => {
      const { card } = await createCardTest({ heading: 'Test Heading', content: 'Test content' })
      await card.updateComplete

      const article = card.querySelector('article')
      const wrapper = article?.querySelector('.pkt-card__wrapper')
      const header = wrapper?.querySelector('.pkt-card__header')
      const content = wrapper?.querySelector('.pkt-card__content')

      expect(wrapper).toBeInTheDocument()
      expect(header).toBeInTheDocument()
      expect(content).toBeInTheDocument()
    })
  })

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

      expect(card.skin).toBe('outlined')
      expect(card.layout).toBe('vertical')
      expect(card.padding).toBe('default')
      expect(card.borderOnHover).toBe(true)
      expect(card.tagPosition).toBe('top')
      expect(card.imageShape).toBe('square')
      expect(card.openLinkInNewTab).toBe(false)
      expect(card.headinglevel).toBe(3)

      const article = card.querySelector('article')
      expect(article).toHaveClass('pkt-card--outlined')
      expect(article).toHaveClass('pkt-card--vertical')
      expect(article).toHaveClass('pkt-card--padding-default')
      expect(article).toHaveClass('pkt-card--border-on-hover')
    })

    test('applies different skin properties correctly', async () => {
      const skins = ['outlined', 'outlined-beige', 'gray', 'beige', 'green', 'blue']

      for (const skin of skins) {
        const { card } = await createCardTest({ skin })
        await card.updateComplete

        expect(card.skin).toBe(skin)
        expect(card.getAttribute('skin')).toBe(skin)

        const article = card.querySelector('article')
        expect(article).toHaveClass(`pkt-card--${skin}`)
      }
    })

    test('rejects unsupported skin values and falls back to default', async () => {
      const unsupportedSkins = ['zebra', 'goldenrod', 'hotpink', 'rainbow', 'invalid']

      for (const invalidSkin of unsupportedSkins) {
        const { card } = await createCardTest({ skin: invalidSkin })
        await card.updateComplete

        // The component should now validate skin values and fall back to default
        expect(card.skin).not.toBe(invalidSkin)
        expect(card.skin).toBe('outlined') // Should fall back to default

        const article = card.querySelector('article')
        // Should not have the invalid CSS class
        expect(article).not.toHaveClass(`pkt-card--${invalidSkin}`)
        // Should have the default skin class instead
        expect(article).toHaveClass('pkt-card--outlined')
      }
    })

    test('validates skin values and logs warnings for invalid skins', async () => {
      // Clear the global spy and create a new one for this specific test
      consoleWarnSpy.mockRestore()
      const localConsoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})

      const { card } = await createCardTest({ skin: 'zebra' })
      await card.updateComplete

      // Should have logged a warning with the correct default value from spec
      expect(localConsoleSpy).toHaveBeenCalledWith(
        'Invalid skin value "zebra". Using default skin "outlined".',
      )

      // Should fall back to default from spec
      expect(card.skin).toBe('outlined')

      // Restore and recreate global spy for subsequent tests
      localConsoleSpy.mockRestore()
      consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
    })

    test('applies different layout properties correctly', async () => {
      const layouts = ['vertical', 'horizontal']

      for (const layout of layouts) {
        const { card } = await createCardTest({ layout })
        await card.updateComplete

        expect(card.layout).toBe(layout)
        expect(card.getAttribute('layout')).toBe(layout)

        const article = card.querySelector('article')
        expect(article).toHaveClass(`pkt-card--${layout}`)
      }
    })

    test('applies different padding properties correctly', async () => {
      const paddingOptions = ['none', 'default']

      for (const padding of paddingOptions) {
        const { card } = await createCardTest({ padding })
        await card.updateComplete

        expect(card.padding).toBe(padding)
        expect(card.getAttribute('padding')).toBe(padding)

        const article = card.querySelector('article')
        expect(article).toHaveClass(`pkt-card--padding-${padding}`)
      }
    })

    test('handles borderOnHover property correctly', async () => {
      // Test with borderOnHover false
      const { card } = await createCardTest()
      card.borderOnHover = false
      await card.updateComplete

      expect(card.borderOnHover).toBe(false)

      const article = card.querySelector('article')
      expect(article).not.toHaveClass('pkt-card--border-on-hover')
    })
  })

  describe('Heading functionality', () => {
    test('renders heading when provided', async () => {
      const { card } = await createCardTest({ heading: 'Test Card Title' })
      await card.updateComplete

      expect(card.heading).toBe('Test Card Title')

      const heading = card.querySelector('pkt-heading')
      expect(heading).toBeInTheDocument()
      expect(heading).toHaveClass('pkt-card__heading')
      expect(heading?.textContent?.trim()).toBe('Test Card Title')
    })

    test('renders subheading when provided', async () => {
      const { card } = await createCardTest({ subheading: 'Test Subheading' })
      await card.updateComplete

      expect(card.subheading).toBe('Test Subheading')

      const subheading = card.querySelector('.pkt-card__subheading')
      expect(subheading).toBeInTheDocument()
      expect(subheading?.textContent).toBe('Test Subheading')
    })

    test('applies correct heading level', async () => {
      const { card } = await createCardTest({ heading: 'Test', headingLevel: 2 })
      await card.updateComplete

      expect(card.headinglevel).toBe(2)

      const heading = card.querySelector('pkt-heading')
      expect(heading?.getAttribute('level')).toBe('2')
    })

    test('does not render header when no heading or subheading', async () => {
      const { card } = await createCardTest()
      await card.updateComplete

      const header = card.querySelector('.pkt-card__header')
      expect(header).not.toBeInTheDocument()
    })
  })

  describe('Link functionality', () => {
    test('renders as regular card when no clickCardLink', async () => {
      const { card } = await createCardTest({ heading: 'Test Title' })
      await card.updateComplete

      expect(card.clickCardLink).toBe(null)

      const heading = card.querySelector('pkt-heading')
      const link = card.querySelector('.pkt-card__link')
      expect(heading).toBeInTheDocument()
      expect(link).not.toBeInTheDocument()

      const article = card.querySelector('article')
      expect(article?.getAttribute('aria-label')).toBe('Test Title')
    })

    test('renders as link card when clickCardLink provided', async () => {
      const { card } = await createCardTest({ heading: 'Test Title' })
      card.clickCardLink = '/test-url'
      await card.updateComplete

      expect(card.clickCardLink).toBe('/test-url')

      const linkHeading = card.querySelector('.pkt-card__link-heading')
      const link = card.querySelector('.pkt-card__link')
      expect(linkHeading).toBeInTheDocument()
      expect(link).toBeInTheDocument()
      expect(link?.getAttribute('href')).toBe('/test-url')
      expect(link?.textContent).toBe('Test Title')

      const article = card.querySelector('article')
      expect(article?.getAttribute('aria-label')).toBe('Test Title lenkekort')
    })

    test('handles openLinkInNewTab correctly', async () => {
      const { card } = await createCardTest({ heading: 'Test' })
      card.clickCardLink = '/test'
      card.openLinkInNewTab = true
      await card.updateComplete

      expect(card.openLinkInNewTab).toBe(true)

      const link = card.querySelector('.pkt-card__link')
      expect(link?.getAttribute('target')).toBe('_blank')
    })

    test('applies correct aria-label for link cards', async () => {
      const { card } = await createCardTest()
      card.clickCardLink = '/test'
      card.ariaLabel = 'Custom aria label'
      await card.updateComplete

      const article = card.querySelector('article')
      expect(article?.getAttribute('aria-label')).toBe('Custom aria label')
    })
  })

  describe('Image functionality', () => {
    test('renders image when provided', async () => {
      const { card } = await createCardTest()
      card.image = { src: '/test-image.jpg', alt: 'Test image' }
      await card.updateComplete

      expect(card.image.src).toBe('/test-image.jpg')
      expect(card.image.alt).toBe('Test image')

      const imageDiv = card.querySelector('.pkt-card__image')
      const img = imageDiv?.querySelector('img')
      expect(imageDiv).toBeInTheDocument()
      expect(img).toBeInTheDocument()
      expect(img?.getAttribute('src')).toBe('/test-image.jpg')
      expect(img?.getAttribute('alt')).toBe('Test image')
    })

    test('does not render image when not provided', async () => {
      const { card } = await createCardTest()
      await card.updateComplete

      const imageDiv = card.querySelector('.pkt-card__image')
      expect(imageDiv).not.toBeInTheDocument()
    })

    test('applies correct image shape classes', async () => {
      const shapes = ['square', 'round'] as const

      for (const shape of shapes) {
        const { card } = await createCardTest()
        card.image = { src: '/test.jpg', alt: 'Test' }
        card.imageShape = shape
        await card.updateComplete

        expect(card.imageShape).toBe(shape)

        const imageDiv = card.querySelector('.pkt-card__image')
        expect(imageDiv).toHaveClass(`pkt-card__image-${shape}`)
      }
    })
  })

  describe('Tags functionality', () => {
    test('renders tags when provided', async () => {
      const { card } = await createCardTest()
      card.tags = [
        { text: 'Tag 1', skin: 'blue' },
        { text: 'Tag 2', skin: 'green' },
      ]
      await card.updateComplete

      expect(card.tags).toHaveLength(2)

      const tagsContainer = card.querySelector('.pkt-card__tags')
      const tags = tagsContainer?.querySelectorAll('pkt-tag')
      expect(tagsContainer).toBeInTheDocument()
      expect(tags).toHaveLength(2)
      expect(tagsContainer?.getAttribute('aria-label')).toBe('merkelapper')
    })

    test('renders single tag with correct aria-label', async () => {
      const { card } = await createCardTest()
      card.tags = [{ text: 'Single Tag' }]
      await card.updateComplete

      const tagsContainer = card.querySelector('.pkt-card__tags')
      expect(tagsContainer?.getAttribute('aria-label')).toBe('merkelapp')
    })

    test('applies correct tag position classes', async () => {
      const positions = ['top', 'bottom'] as const

      for (const position of positions) {
        const { card, container } = await createCardTest()
        card.tags = [{ text: 'Test Tag' }]
        card.tagPosition = position
        await card.updateComplete

        expect(card.tagPosition).toBe(position)

        const tagsContainer = card.querySelector('.pkt-card__tags')
        expect(tagsContainer).toHaveClass(`pkt-card__tags-${position}`)

        // Cleanup for next iteration
        container.remove()
      }
    })

    test('does not render tags when array is empty', async () => {
      const { card } = await createCardTest()
      await card.updateComplete

      const tagsContainer = card.querySelector('.pkt-card__tags')
      expect(tagsContainer).not.toBeInTheDocument()
    })
  })

  describe('Metadata functionality', () => {
    test('renders metadata when provided', async () => {
      const { card } = await createCardTest()
      card.metaLead = 'Author Name'
      card.metaTrail = '2023-12-01'
      await card.updateComplete

      expect(card.metaLead).toBe('Author Name')
      expect(card.metaTrail).toBe('2023-12-01')

      const metadata = card.querySelector('.pkt-card__metadata')
      const metaLead = metadata?.querySelector('.pkt-card__metadata-lead')
      const metaTrail = metadata?.querySelector('.pkt-card__metadata-trail')

      expect(metadata).toBeInTheDocument()
      expect(metaLead).toBeInTheDocument()
      expect(metaTrail).toBeInTheDocument()
      expect(metaLead?.textContent).toBe('Author Name')
      expect(metaTrail?.textContent).toBe('2023-12-01')
    })

    test('renders only metaLead when metaTrail not provided', async () => {
      const { card } = await createCardTest()
      card.metaLead = 'Author Only'
      await card.updateComplete

      const metadata = card.querySelector('.pkt-card__metadata')
      const metaLead = metadata?.querySelector('.pkt-card__metadata-lead')
      const metaTrail = metadata?.querySelector('.pkt-card__metadata-trail')

      expect(metadata).toBeInTheDocument()
      expect(metaLead).toBeInTheDocument()
      expect(metaTrail).not.toBeInTheDocument()
      expect(metaLead?.textContent).toBe('Author Only')
    })

    test('renders only metaTrail when metaLead not provided', async () => {
      const { card } = await createCardTest()
      card.metaTrail = 'Date Only'
      await card.updateComplete

      const metadata = card.querySelector('.pkt-card__metadata')
      const metaLead = metadata?.querySelector('.pkt-card__metadata-lead')
      const metaTrail = metadata?.querySelector('.pkt-card__metadata-trail')

      expect(metadata).toBeInTheDocument()
      expect(metaLead).not.toBeInTheDocument()
      expect(metaTrail).toBeInTheDocument()
      expect(metaTrail?.textContent).toBe('Date Only')
    })

    test('does not render metadata when neither provided', async () => {
      const { card } = await createCardTest()
      await card.updateComplete

      const metadata = card.querySelector('.pkt-card__metadata')
      expect(metadata).not.toBeInTheDocument()
    })
  })

  describe('Content placement and structure', () => {
    test('renders content elements in correct order', async () => {
      const { card } = await createCardTest({
        heading: 'Test Title',
        subheading: 'Test Sub',
      })
      card.tags = [{ text: 'Test Tag' }]
      card.image = { src: '/test.jpg', alt: 'Test' }
      card.metaLead = 'Author'
      card.metaTrail = 'Date'
      await card.updateComplete

      const article = card.querySelector('article')
      const children = Array.from(article?.children || [])

      // Should have image first, then wrapper
      expect(children[0]).toHaveClass('pkt-card__image')
      expect(children[1]).toHaveClass('pkt-card__wrapper')

      const wrapper = children[1]
      const wrapperChildren = Array.from(wrapper?.children || [])

      // Order within wrapper: tags (top), header, content, metadata
      expect(wrapperChildren[0]).toHaveClass('pkt-card__tags-top')
      expect(wrapperChildren[1]).toHaveClass('pkt-card__header')
      expect(wrapperChildren[2]).toHaveClass('pkt-card__content')
      expect(wrapperChildren[3]).toHaveClass('pkt-card__metadata')
    })

    test('places tags at bottom when tagPosition is bottom', async () => {
      const { card } = await createCardTest({ heading: 'Test Title' })
      card.tags = [{ text: 'Test Tag' }]
      card.tagPosition = 'bottom'
      await card.updateComplete

      const wrapper = card.querySelector('.pkt-card__wrapper')
      const wrapperChildren = Array.from(wrapper?.children || [])

      // Order: header, content, tags (bottom)
      expect(wrapperChildren[0]).toHaveClass('pkt-card__header')
      expect(wrapperChildren[1]).toHaveClass('pkt-card__content')
      expect(wrapperChildren[2]).toHaveClass('pkt-card__tags-bottom')
    })
  })

  describe('Accessibility', () => {
    test('has no accessibility violations', async () => {
      const { card } = await createCardTest({
        heading: 'Accessible Card',
      })
      await card.updateComplete
      await card.updateComplete

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

    test('applies correct ARIA attributes', async () => {
      const { card } = await createCardTest({ heading: 'Test' })
      card.ariaLabel = 'Custom accessible label'
      await card.updateComplete

      expect(card.ariaLabel).toBe('Custom accessible label')

      const article = card.querySelector('article')
      expect(article?.getAttribute('aria-label')).toBe('Custom accessible label')
    })

    test('falls back to heading for aria-label when no explicit aria-label', async () => {
      const { card } = await createCardTest({ heading: 'Default Aria Label' })
      await card.updateComplete

      const article = card.querySelector('article')
      expect(article?.getAttribute('aria-label')).toBe('Default Aria Label')
    })

    test('falls back to "kort" when no heading or aria-label', async () => {
      const { card } = await createCardTest()
      await card.updateComplete

      const article = card.querySelector('article')
      expect(article?.getAttribute('aria-label')).toBe('kort')
    })
  })
})
