import '@testing-library/jest-dom'

import { type ChangeEvent, useState } from 'react'
import { act, fireEvent, render, waitFor } from '@testing-library/react'
import { vi } from 'vitest'

import { IPktDatepicker, PktDatepicker } from './Datepicker'

const datePickerId = 'datepickerId'
const label = 'Date Picker Label'

const createDatepickerTest = (props: Partial<IPktDatepicker> = {}) => {
  const defaultProps: IPktDatepicker = {
    label,
    id: datePickerId,
    ...props,
  }

  return render(<PktDatepicker {...defaultProps} />)
}

describe('PktDatepicker', () => {
  describe('Multiple date selection', () => {
    test('displays multiple selected dates as tags', () => {
      const { container } = createDatepickerTest({
        multiple: true,
        value: ['2024-06-15', '2024-06-20', '2024-06-25'],
      })

      const tags = container.querySelectorAll('.pkt-date-tags .pkt-tag')
      expect(tags.length).toBe(3)
    })

    test('removes dates when clicking tag close button', async () => {
      const handleValueChange = vi.fn()
      const { container } = createDatepickerTest({
        multiple: true,
        value: ['2024-06-15', '2024-06-20'],
        onValueChange: handleValueChange,
      })

      const closeButtons = container.querySelectorAll('.pkt-date-tags button.pkt-tag')
      expect(closeButtons.length).toBe(2)

      fireEvent.click(closeButtons[0])

      await waitFor(() => {
        expect(handleValueChange).toHaveBeenCalledWith(['2024-06-20'])
      })
    })

    test('respects maxlength in multiple mode', () => {
      const { container } = createDatepickerTest({
        multiple: true,
        maxlength: 2,
        value: ['2024-06-15', '2024-06-20'],
      })

      const input = container.querySelector('input[type="date"]') as HTMLInputElement
      expect(input).toBeDisabled()
    })

    test('renders counter when maxlength is set', () => {
      const { container } = createDatepickerTest({
        multiple: true,
        maxlength: 5,
        value: ['2024-06-15', '2024-06-20'],
      })

      const counter = container.querySelector('.pkt-input__counter')
      expect(counter).toBeInTheDocument()
    })

    test('does not disable input when maxlength is not reached', () => {
      const { container } = createDatepickerTest({
        multiple: true,
        maxlength: 5,
        value: ['2024-06-15', '2024-06-20'],
      })

      const input = container.querySelector('input[type="date"]') as HTMLInputElement
      expect(input).not.toBeDisabled()
    })

    test('sorts multiple dates chronologically', () => {
      const { container } = createDatepickerTest({
        multiple: true,
        value: ['2024-06-25', '2024-06-15', '2024-06-20'],
      })

      const times = container.querySelectorAll('.pkt-date-tags time')
      expect(times[0]).toHaveAttribute('datetime', '2024-06-15')
      expect(times[1]).toHaveAttribute('datetime', '2024-06-20')
      expect(times[2]).toHaveAttribute('datetime', '2024-06-25')
    })

    test('sorts multiple dates chronologically across months and years', () => {
      const { container } = createDatepickerTest({
        multiple: true,
        value: ['2025-01-10', '2024-12-25', '2024-06-15'],
      })

      const times = container.querySelectorAll('.pkt-date-tags time')
      expect(times[0]).toHaveAttribute('datetime', '2024-06-15')
      expect(times[1]).toHaveAttribute('datetime', '2024-12-25')
      expect(times[2]).toHaveAttribute('datetime', '2025-01-10')
    })

    test('dispatches onValueChange with remaining dates when tag removed', async () => {
      const handleValueChange = vi.fn()
      const { container } = createDatepickerTest({
        multiple: true,
        value: ['2024-06-15', '2024-06-20', '2024-06-25'],
        onValueChange: handleValueChange,
      })

      const closeButtons = container.querySelectorAll('.pkt-date-tags button.pkt-tag')
      fireEvent.click(closeButtons[1]) // remove middle date (2024-06-20)

      await waitFor(() => {
        expect(handleValueChange).toHaveBeenCalledWith(['2024-06-15', '2024-06-25'])
      })
    })

    test('renders date tags with formatted dates', () => {
      const { container } = createDatepickerTest({
        multiple: true,
        value: ['2024-06-15'],
        dateformat: 'yyyy-MM-dd',
      })

      const time = container.querySelector('.pkt-date-tags time')
      expect(time).toBeInTheDocument()
      expect(time).toHaveTextContent('2024-06-15')
    })

    test('renders date tags with default dd.MM.yyyy format', () => {
      const { container } = createDatepickerTest({
        multiple: true,
        value: ['2024-06-15'],
      })

      const time = container.querySelector('.pkt-date-tags time')
      expect(time).toBeInTheDocument()
      expect(time).toHaveTextContent('15.06.2024')
    })
  })

  describe('Range selection', () => {
    test('renders two inputs for range mode', () => {
      const { container } = createDatepickerTest({
        range: true,
      })

      const inputs = container.querySelectorAll('input[type="date"]')
      expect(inputs.length).toBe(2)
    })

    test('populates both input fields when initialized with range value', () => {
      const { container } = createDatepickerTest({
        range: true,
        value: ['2024-06-15', '2024-06-25'],
      })

      const inputs = container.querySelectorAll('input[type="date"]') as NodeListOf<HTMLInputElement>
      expect(inputs[0].value).toBe('2024-06-15')
      expect(inputs[1].value).toBe('2024-06-25')
    })

    test('displays range labels when showRangeLabels is true', () => {
      const { container } = createDatepickerTest({
        range: true,
        showRangeLabels: true,
      })

      const prefix = container.querySelector('.pkt-input-prefix')
      expect(prefix).toBeInTheDocument()
      expect(prefix).toHaveTextContent('Fra')
    })

    test('renders separator between range inputs', () => {
      const { container } = createDatepickerTest({
        range: true,
      })

      const separator = container.querySelector('.pkt-input-separator')
      expect(separator).toBeInTheDocument()
      expect(separator).toHaveTextContent('–')
    })

    test('hides separator when showRangeLabels is true', () => {
      const { container } = createDatepickerTest({
        range: true,
        showRangeLabels: true,
      })

      const separator = container.querySelector('.pkt-input-separator')
      expect(separator).not.toBeInTheDocument()
    })

    test('dispatches onValueChange for partial range (only from)', () => {
      const handleValueChange = vi.fn()
      const { container } = createDatepickerTest({
        range: true,
        onValueChange: handleValueChange,
      })

      const inputs = container.querySelectorAll('input[type="date"]') as NodeListOf<HTMLInputElement>
      fireEvent.change(inputs[0], { target: { value: '2024-06-15' } })

      expect(handleValueChange).toHaveBeenCalledWith(['2024-06-15'])
    })

    test('dispatches onValueChange when both range dates are present', () => {
      const handleValueChange = vi.fn()
      const { container } = createDatepickerTest({
        range: true,
        value: ['2024-06-15'],
        onValueChange: handleValueChange,
      })

      const inputs = container.querySelectorAll('input[type="date"]') as NodeListOf<HTMLInputElement>
      fireEvent.change(inputs[1], { target: { value: '2024-06-25' } })

      expect(handleValueChange).toHaveBeenCalledWith(['2024-06-15', '2024-06-25'])
    })

    test('validates range order on blur (rejects end before start)', () => {
      const handleValueChange = vi.fn()
      const { container } = createDatepickerTest({
        range: true,
        value: ['2024-06-25', '2024-06-15'],
        onValueChange: handleValueChange,
      })

      const inputs = container.querySelectorAll('input[type="date"]') as NodeListOf<HTMLInputElement>
      fireEvent.blur(inputs[1])

      // Should reset to just the from-date since range order is invalid
      expect(handleValueChange).toHaveBeenCalledWith(['2024-06-25'])
    })

    test('applies range input classes to container', () => {
      const { container } = createDatepickerTest({
        range: true,
        showRangeLabels: true,
      })

      const inputsDiv = container.querySelector('.pkt-datepicker__inputs')
      expect(inputsDiv).toHaveClass('pkt-input__range-inputs')
    })

    test('range inputs have aria-labels with label and from/to strings', () => {
      const { container } = createDatepickerTest({
        range: true,
      })

      const fromInput = container.querySelector(`#${datePickerId}-input`) as HTMLInputElement
      expect(fromInput).toHaveAttribute('aria-label', `${label} Fra`)

      const toInput = container.querySelector(`#${datePickerId}-to`) as HTMLInputElement
      expect(toInput).toHaveAttribute('aria-label', `${label} Til`)
    })

    test('controlled range with onChange updates value via useState', async () => {
      function ControlledRange() {
        const [val, setVal] = useState('2024-06-15,2024-06-25')
        return (
          <>
            <PktDatepicker
              id="range-controlled"
              label="Range"
              range
              value={val}
              onChange={(e: ChangeEvent<HTMLInputElement>) => setVal(e.target.value)}
            />
            <span data-testid="output">{val}</span>
          </>
        )
      }

      const { container, getByTestId } = render(<ControlledRange />)
      const inputs = container.querySelectorAll('input[type="date"]') as NodeListOf<HTMLInputElement>

      // Change the "from" date
      fireEvent.change(inputs[0], { target: { value: '2024-06-10' } })

      await waitFor(() => {
        expect(getByTestId('output').textContent).toBe('2024-06-10,2024-06-25')
      })

      // Change the "to" date
      fireEvent.change(inputs[1], { target: { value: '2024-06-30' } })

      await waitFor(() => {
        expect(getByTestId('output').textContent).toBe('2024-06-10,2024-06-30')
      })
    })
  })

  describe('Single date selection', () => {
    test('dispatches onChange when input value changes', () => {
      const handleChange = vi.fn()
      const { container } = createDatepickerTest({
        onChange: handleChange,
      })

      const input = container.querySelector('input[type="date"]') as HTMLInputElement
      fireEvent.change(input, { target: { value: '2024-06-15' } })

      expect(handleChange).toHaveBeenCalled()
    })

    test('dispatches onValueChange with array when input value changes', () => {
      const handleValueChange = vi.fn()
      const { container } = createDatepickerTest({
        onValueChange: handleValueChange,
      })

      const input = container.querySelector('input[type="date"]') as HTMLInputElement
      fireEvent.change(input, { target: { value: '2024-06-15' } })

      expect(handleValueChange).toHaveBeenCalledWith(['2024-06-15'])
    })

    test('dispatches onValueChange with empty array when cleared', () => {
      const handleValueChange = vi.fn()
      const { container } = createDatepickerTest({
        value: '2024-06-15',
        onValueChange: handleValueChange,
      })

      const input = container.querySelector('input[type="date"]') as HTMLInputElement
      fireEvent.change(input, { target: { value: '' } })

      expect(handleValueChange).toHaveBeenCalledWith([])
    })
  })

  describe('Form reset', () => {
    beforeEach(() => {
      vi.useFakeTimers()
    })
    afterEach(() => {
      vi.useRealTimers()
    })

    test('resets single value to defaultValue on form reset', () => {
      const { container } = render(
        <form>
          <PktDatepicker id={datePickerId} label={label} defaultValue="2024-06-15" />
        </form>,
      )

      const input = container.querySelector('input[type="date"]') as HTMLInputElement
      fireEvent.change(input, { target: { value: '2024-12-25' } })

      const formInput = container.querySelector('input.pkt-visually-hidden') as HTMLInputElement
      expect(formInput.value).toBe('2024-12-25')

      const form = container.querySelector('form')!
      act(() => {
        fireEvent.reset(form)
        vi.advanceTimersByTime(10)
      })

      expect(formInput.value).toBe('2024-06-15')
    })

    test('resets to empty when no defaultValue on form reset', () => {
      const { container } = render(
        <form>
          <PktDatepicker id={datePickerId} label={label} />
        </form>,
      )

      const input = container.querySelector('input[type="date"]') as HTMLInputElement
      fireEvent.change(input, { target: { value: '2024-06-15' } })

      const formInput = container.querySelector('input.pkt-visually-hidden') as HTMLInputElement
      expect(formInput.value).toBe('2024-06-15')

      const form = container.querySelector('form')!
      act(() => {
        fireEvent.reset(form)
        vi.advanceTimersByTime(10)
      })

      expect(formInput.value).toBe('')
    })
  })
})
