import type { Injector } from '@furystack/inject'
import { createInjector } from '@furystack/inject'
import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'
import { usingAsync } from '@furystack/utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { ThemeProviderService } from '../../services/theme-provider-service.js'
import { Form, FormContextToken } from '../form.js'
import { Switch } from './switch.js'

describe('Switch', () => {
  beforeEach(() => {
    document.body.innerHTML = '<div id="root"></div>'
  })

  afterEach(() => {
    document.body.innerHTML = ''
    vi.restoreAllMocks()
  })

  it('should render as custom element', async () => {
    await usingAsync(createInjector(), async (injector) => {
      const rootElement = document.getElementById('root') as HTMLDivElement

      initializeShadeRoot({
        injector,
        rootElement,
        jsxElement: <Switch />,
      })

      await flushUpdates()

      const switchEl = document.querySelector('shade-switch')
      expect(switchEl).not.toBeNull()
    })
  })

  it('should render the hidden checkbox input', async () => {
    await usingAsync(createInjector(), async (injector) => {
      const rootElement = document.getElementById('root') as HTMLDivElement

      initializeShadeRoot({
        injector,
        rootElement,
        jsxElement: <Switch name="testSwitch" />,
      })

      await flushUpdates()

      const input = document.querySelector('shade-switch input[type="checkbox"]') as HTMLInputElement
      expect(input).not.toBeNull()
      expect(input.name).toBe('testSwitch')
      expect(input.getAttribute('role')).toBe('switch')
    })
  })

  it('should render the switch track and thumb', async () => {
    await usingAsync(createInjector(), async (injector) => {
      const rootElement = document.getElementById('root') as HTMLDivElement

      initializeShadeRoot({
        injector,
        rootElement,
        jsxElement: <Switch />,
      })

      await flushUpdates()

      const track = document.querySelector('shade-switch .switch-track')
      const thumb = document.querySelector('shade-switch .switch-thumb')
      expect(track).not.toBeNull()
      expect(thumb).not.toBeNull()
    })
  })

  it('should render the label title', async () => {
    await usingAsync(createInjector(), async (injector) => {
      const rootElement = document.getElementById('root') as HTMLDivElement

      initializeShadeRoot({
        injector,
        rootElement,
        jsxElement: <Switch labelTitle="Enable notifications" />,
      })

      await flushUpdates()

      const label = document.querySelector('shade-switch label') as HTMLLabelElement
      expect(label).not.toBeNull()
      expect(label.textContent).toContain('Enable notifications')
    })
  })

  it('should not render label span when labelTitle is not provided', async () => {
    await usingAsync(createInjector(), async (injector) => {
      const rootElement = document.getElementById('root') as HTMLDivElement

      initializeShadeRoot({
        injector,
        rootElement,
        jsxElement: <Switch />,
      })

      await flushUpdates()

      const labelSpan = document.querySelector('shade-switch .switch-label')
      expect(labelSpan).toBeNull()
    })
  })

  describe('checked state', () => {
    it('should render as checked when checked prop is true', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch checked />,
        })

        await flushUpdates()

        const input = document.querySelector('shade-switch input[type="checkbox"]') as HTMLInputElement
        expect(input.checked).toBe(true)
      })
    })

    it('should render as unchecked when checked prop is false', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch checked={false} />,
        })

        await flushUpdates()

        const input = document.querySelector('shade-switch input[type="checkbox"]') as HTMLInputElement
        expect(input.checked).toBe(false)
      })
    })
  })

  describe('disabled state', () => {
    it('should set data-disabled attribute when disabled', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch disabled />,
        })

        await flushUpdates()

        const switchEl = document.querySelector('shade-switch') as HTMLElement
        expect(switchEl.hasAttribute('data-disabled')).toBe(true)
      })
    })

    it('should not have data-disabled attribute when not disabled', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch disabled={false} />,
        })

        await flushUpdates()

        const switchEl = document.querySelector('shade-switch') as HTMLElement
        expect(switchEl.hasAttribute('data-disabled')).toBe(false)
      })
    })

    it('should set the disabled attribute on the input element', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch disabled />,
        })

        await flushUpdates()

        const input = document.querySelector('shade-switch input[type="checkbox"]') as HTMLInputElement
        expect(input.disabled).toBe(true)
      })
    })
  })

  describe('size', () => {
    it('should set data-size="small" when size is small', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch size="small" />,
        })

        await flushUpdates()

        const switchEl = document.querySelector('shade-switch') as HTMLElement
        expect(switchEl.getAttribute('data-size')).toBe('small')
      })
    })

    it('should not have data-size attribute when size is medium (default)', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch size="medium" />,
        })

        await flushUpdates()

        const switchEl = document.querySelector('shade-switch') as HTMLElement
        expect(switchEl.hasAttribute('data-size')).toBe(false)
      })
    })
  })

  describe('callbacks', () => {
    it('should call onchange when switch is toggled', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement
        const onchange = vi.fn()

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch name="toggle" onchange={onchange} />,
        })

        await flushUpdates()

        const input = document.querySelector('shade-switch input[type="checkbox"]') as HTMLInputElement
        input.dispatchEvent(new Event('change', { bubbles: true }))

        await flushUpdates()

        expect(onchange).toHaveBeenCalledOnce()
      })
    })
  })

  describe('theme integration', () => {
    it('should set CSS color variable from theme', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch />,
        })

        await flushUpdates()

        const switchEl = document.querySelector('shade-switch') as HTMLElement
        const themeService = injector.get(ThemeProviderService)
        expect(switchEl.style.getPropertyValue('--switch-color')).toBe(themeService.theme.palette.primary.main)
      })
    })

    it('should use custom color from color prop', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch color="secondary" />,
        })

        await flushUpdates()

        const switchEl = document.querySelector('shade-switch') as HTMLElement
        const themeService = injector.get(ThemeProviderService)
        expect(switchEl.style.getPropertyValue('--switch-color')).toBe(themeService.theme.palette.secondary.main)
      })
    })
  })

  describe('FormService integration', () => {
    it('should register input with FormService when inside a Form', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        type TestFormData = { notifications: string }

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: (
            <Form<TestFormData> onSubmit={() => {}} validate={(_data): _data is TestFormData => true}>
              <Switch name="notifications" labelTitle="Enable notifications" />
            </Form>
          ),
        })

        await flushUpdates()

        const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
        const formInjector = (form as unknown as { injector: Injector }).injector
        const formService = formInjector.get(FormContextToken)!

        expect(formService.inputs.size).toBe(1)
      })
    })

    it('should unregister input from FormService on cleanup', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        type TestFormData = { notifications: string }

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: (
            <Form<TestFormData> onSubmit={() => {}} validate={(_data): _data is TestFormData => true}>
              <Switch name="notifications" labelTitle="Enable notifications" />
            </Form>
          ),
        })

        await flushUpdates()

        const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
        const formInjector = (form as unknown as { injector: Injector }).injector
        const formService = formInjector.get(FormContextToken)!

        expect(formService.inputs.size).toBe(1)

        rootElement.innerHTML = ''

        await flushUpdates()
      })
    })
  })

  describe('labelProps', () => {
    it('should pass labelProps to the label element', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch labelProps={{ className: 'custom-label' }} />,
        })

        await flushUpdates()

        const label = document.querySelector('shade-switch label') as HTMLLabelElement
        expect(label.className).toContain('custom-label')
      })
    })
  })

  describe('required', () => {
    it('should set the required attribute on the input element', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch required />,
        })

        await flushUpdates()

        const input = document.querySelector('shade-switch input[type="checkbox"]') as HTMLInputElement
        expect(input.required).toBe(true)
      })
    })
  })

  describe('value', () => {
    it('should set the value attribute on the input element', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <Switch value="yes" />,
        })

        await flushUpdates()

        const input = document.querySelector('shade-switch input[type="checkbox"]') as HTMLInputElement
        expect(input.value).toBe('yes')
      })
    })

    it('should fall back to the native "on" default when value prop is omitted', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        type TestFormData = { notifications: string }

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: (
            <Form<TestFormData> onSubmit={() => {}} validate={(_data): _data is TestFormData => true}>
              <Switch name="notifications" labelTitle="Enable notifications" checked />
            </Form>
          ),
        })

        await flushUpdates()

        const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
        const input = form.querySelector('input[type="checkbox"]') as HTMLInputElement

        expect(input.hasAttribute('value')).toBe(false)
        expect(Object.fromEntries(new FormData(form).entries())).toEqual({ notifications: 'on' })
      })
    })

    it('should propagate change events to the parent Form so rawFormData updates', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        type TestFormData = { notifications: string }

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: (
            <Form<TestFormData> onSubmit={() => {}} validate={(_data): _data is TestFormData => true}>
              <Switch name="notifications" value="yes" labelTitle="Enable notifications" />
            </Form>
          ),
        })

        await flushUpdates()

        const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
        const input = form.querySelector('input[type="checkbox"]') as HTMLInputElement
        const formInjector = (form as unknown as { injector: Injector }).injector
        const formService = formInjector.get(FormContextToken)!

        input.checked = true
        input.dispatchEvent(new Event('change', { bubbles: true }))

        await flushUpdates()

        expect(formService.rawFormData.getValue()).toEqual({ notifications: 'yes' })
      })
    })
  })

  describe('size', () => {
    it('should not set data-size when size is not specified', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement
        initializeShadeRoot({ injector, rootElement, jsxElement: <Switch /> })
        await flushUpdates()
        const el = document.querySelector('shade-switch') as HTMLElement
        expect(el.getAttribute('data-size')).toBeNull()
      })
    })

    it('should not set data-size for medium size (default)', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement
        initializeShadeRoot({ injector, rootElement, jsxElement: <Switch size="medium" /> })
        await flushUpdates()
        const el = document.querySelector('shade-switch') as HTMLElement
        expect(el.getAttribute('data-size')).toBeNull()
      })
    })

    it('should set data-size="small" for small size', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement
        initializeShadeRoot({ injector, rootElement, jsxElement: <Switch size="small" /> })
        await flushUpdates()
        const el = document.querySelector('shade-switch') as HTMLElement
        expect(el.getAttribute('data-size')).toBe('small')
      })
    })

    it('should set data-size="large" for large size', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement
        initializeShadeRoot({ injector, rootElement, jsxElement: <Switch size="large" /> })
        await flushUpdates()
        const el = document.querySelector('shade-switch') as HTMLElement
        expect(el.getAttribute('data-size')).toBe('large')
      })
    })
  })
})
