import {
  render,
  screen,
  fireEvent,
  renderHook,
  act,
} from '@testing-library/react'
import {describe, expect, it, vi, beforeEach} from 'vitest'

import {ImagePickerProvider, useImagePickerContext} from './ImagePickerProvider'

describe('ImagePickerProvider', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  describe('Context', () => {
    it('throws error when used outside of provider', () => {
      // Silence console.error for this test
      const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})

      expect(() => {
        renderHook(() => useImagePickerContext())
      }).toThrow(
        'useImagePickerContext must be used within an ImagePickerProvider'
      )

      consoleSpy.mockRestore()
    })

    it('provides context value when used within provider', () => {
      const {result} = renderHook(() => useImagePickerContext(), {
        wrapper: ({children}) => (
          <ImagePickerProvider>{children}</ImagePickerProvider>
        ),
      })

      expect(result.current).toHaveProperty('openCamera')
      expect(result.current).toHaveProperty('openGallery')
      expect(typeof result.current.openCamera).toBe('function')
      expect(typeof result.current.openGallery).toBe('function')
    })
  })

  describe('Hidden Inputs', () => {
    it('renders hidden file inputs', () => {
      const {container} = render(
        <ImagePickerProvider>
          <div>Test Content</div>
        </ImagePickerProvider>
      )

      const inputs = container.querySelectorAll('input[type="file"]')
      expect(inputs).toHaveLength(3)

      // Gallery input
      const galleryInput = inputs[0]
      expect(galleryInput).toHaveAttribute('accept', 'image/*')
      expect(galleryInput).not.toHaveAttribute('capture')
      expect(galleryInput).toHaveStyle({display: 'none'})

      // Front camera input
      const frontCameraInput = inputs[1]
      expect(frontCameraInput).toHaveAttribute('accept', 'image/*')
      expect(frontCameraInput).toHaveAttribute('capture', 'user')

      // Back camera input
      const backCameraInput = inputs[2]
      expect(backCameraInput).toHaveAttribute('accept', 'image/*')
      expect(backCameraInput).toHaveAttribute('capture', 'environment')
    })
  })

  describe('openGallery', () => {
    it('triggers gallery input click and resolves with selected file', async () => {
      const TestComponent = () => {
        const {openGallery} = useImagePickerContext()

        return (
          <button
            type="button"
            onClick={() =>
              openGallery()
                .then(file => {
                  const span = document.createElement('span')
                  span.textContent = file.name
                  span.setAttribute('data-testid', 'selected-file')
                  document.body.appendChild(span)
                })
                .catch(() => {
                  // Ignore errors from cleanup
                })
            }
          >
            Open Gallery
          </button>
        )
      }

      const {container} = render(
        <ImagePickerProvider>
          <TestComponent />
        </ImagePickerProvider>
      )

      const galleryInput = container.querySelector(
        'input[type="file"]:not([capture])'
      ) as HTMLInputElement
      const clickSpy = vi.spyOn(galleryInput, 'click')

      // Click the button to open gallery
      const button = screen.getByText('Open Gallery')
      fireEvent.click(button)

      expect(clickSpy).toHaveBeenCalledTimes(1)

      // Simulate file selection
      const file = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
      Object.defineProperty(galleryInput, 'files', {
        value: [file],
        configurable: true,
      })

      await act(async () => {
        fireEvent.change(galleryInput)
      })

      // Check that the promise resolved with the file
      await vi.waitFor(() => {
        const selectedFile = document.querySelector(
          '[data-testid="selected-file"]'
        )
        expect(selectedFile?.textContent).toBe('test.jpg')
      })
    })

    it('handles cancel event', async () => {
      const TestComponent = () => {
        const {openGallery} = useImagePickerContext()

        return (
          <button
            type="button"
            onClick={() =>
              openGallery().catch(error => {
                const span = document.createElement('span')
                span.textContent = error.message
                span.setAttribute('data-testid', 'cancel-message')
                document.body.appendChild(span)
              })
            }
          >
            Open Gallery
          </button>
        )
      }

      const {container} = render(
        <ImagePickerProvider>
          <TestComponent />
        </ImagePickerProvider>
      )

      const galleryInput = container.querySelector(
        'input[type="file"]:not([capture])'
      ) as HTMLInputElement

      const button = screen.getByText('Open Gallery')
      fireEvent.click(button)

      // Simulate cancel event
      await act(async () => {
        const cancelEvent = new Event('cancel', {bubbles: true})
        galleryInput.dispatchEvent(cancelEvent)
      })

      await vi.waitFor(() => {
        const cancelMessage = document.querySelector(
          '[data-testid="cancel-message"]'
        )
        expect(cancelMessage?.textContent).toBe('User cancelled file selection')
      })
    })
  })

  describe('openCamera', () => {
    it('triggers back camera input click by default', async () => {
      const TestComponent = () => {
        const {openCamera} = useImagePickerContext()

        return (
          <button
            type="button"
            onClick={() =>
              openCamera().catch(() => {
                // Ignore errors from cleanup
              })
            }
          >
            Open Camera
          </button>
        )
      }

      const {container} = render(
        <ImagePickerProvider>
          <TestComponent />
        </ImagePickerProvider>
      )

      const backCameraInput = container.querySelector(
        'input[capture="environment"]'
      ) as HTMLInputElement
      const clickSpy = vi.spyOn(backCameraInput, 'click')

      const button = screen.getByText('Open Camera')
      fireEvent.click(button)

      expect(clickSpy).toHaveBeenCalledTimes(1)
    })

    it('triggers front camera input click when specified', async () => {
      const TestComponent = () => {
        const {openCamera} = useImagePickerContext()

        return (
          <button
            type="button"
            onClick={() =>
              openCamera('front').catch(() => {
                // Ignore errors from cleanup
              })
            }
          >
            Open Front Camera
          </button>
        )
      }

      const {container} = render(
        <ImagePickerProvider>
          <TestComponent />
        </ImagePickerProvider>
      )

      const frontCameraInput = container.querySelector(
        'input[capture="user"]'
      ) as HTMLInputElement
      const clickSpy = vi.spyOn(frontCameraInput, 'click')

      const button = screen.getByText('Open Front Camera')
      fireEvent.click(button)

      expect(clickSpy).toHaveBeenCalledTimes(1)
    })

    it('resolves with selected file from camera', async () => {
      const TestComponent = () => {
        const {openCamera} = useImagePickerContext()

        return (
          <button
            type="button"
            onClick={() =>
              openCamera()
                .then(file => {
                  const span = document.createElement('span')
                  span.textContent = file.name
                  span.setAttribute('data-testid', 'camera-file')
                  document.body.appendChild(span)
                })
                .catch(() => {
                  // Ignore errors from cleanup
                })
            }
          >
            Open Camera
          </button>
        )
      }

      const {container} = render(
        <ImagePickerProvider>
          <TestComponent />
        </ImagePickerProvider>
      )

      const cameraInput = container.querySelector(
        'input[capture="environment"]'
      ) as HTMLInputElement

      const button = screen.getByText('Open Camera')
      fireEvent.click(button)

      // Simulate file capture
      const file = new File(['photo'], 'photo.jpg', {type: 'image/jpeg'})
      Object.defineProperty(cameraInput, 'files', {
        value: [file],
        configurable: true,
      })

      await act(async () => {
        fireEvent.change(cameraInput)
      })

      await vi.waitFor(() => {
        const cameraFile = document.querySelector('[data-testid="camera-file"]')
        expect(cameraFile?.textContent).toBe('photo.jpg')
      })
    })

    it('handles cancel event for camera', async () => {
      const TestComponent = () => {
        const {openCamera} = useImagePickerContext()

        return (
          <button
            type="button"
            onClick={() =>
              openCamera().catch(error => {
                const span = document.createElement('span')
                span.textContent = error.message
                span.setAttribute('data-testid', 'camera-cancel')
                document.body.appendChild(span)
              })
            }
          >
            Open Camera
          </button>
        )
      }

      const {container} = render(
        <ImagePickerProvider>
          <TestComponent />
        </ImagePickerProvider>
      )

      const cameraInput = container.querySelector(
        'input[capture="environment"]'
      ) as HTMLInputElement

      const button = screen.getByText('Open Camera')
      fireEvent.click(button)

      // Simulate cancel event
      await act(async () => {
        const cancelEvent = new Event('cancel', {bubbles: true})
        cameraInput.dispatchEvent(cancelEvent)
      })

      await vi.waitFor(() => {
        const cancelMessage = document.querySelector(
          '[data-testid="camera-cancel"]'
        )
        expect(cancelMessage?.textContent).toBe('User cancelled camera')
      })
    })
  })

  describe('Multiple Picker Handling', () => {
    it('rejects previous promise when new picker is opened', async () => {
      const TestComponent = () => {
        const {openCamera, openGallery} = useImagePickerContext()

        return (
          <>
            <button
              type="button"
              onClick={() =>
                openGallery().catch(error => {
                  const span = document.createElement('span')
                  span.textContent = error.message
                  span.setAttribute('data-testid', 'gallery-error')
                  document.body.appendChild(span)
                })
              }
            >
              Open Gallery
            </button>
            <button
              type="button"
              onClick={() =>
                openCamera().catch(() => {
                  // Ignore errors from cleanup
                })
              }
            >
              Open Camera
            </button>
          </>
        )
      }

      render(
        <ImagePickerProvider>
          <TestComponent />
        </ImagePickerProvider>
      )

      // Open gallery first
      const galleryButton = screen.getByText('Open Gallery')
      fireEvent.click(galleryButton)

      // Then immediately open camera
      const cameraButton = screen.getByText('Open Camera')
      fireEvent.click(cameraButton)

      await vi.waitFor(() => {
        const errorMessage = document.querySelector(
          '[data-testid="gallery-error"]'
        )
        expect(errorMessage?.textContent).toBe(
          'New file picker opened before previous completed'
        )
      })
    })
  })

  describe('Cleanup', () => {
    it('clears input value after file selection', async () => {
      const TestComponent = () => {
        const {openGallery} = useImagePickerContext()

        return (
          <button
            type="button"
            onClick={() =>
              openGallery().catch(() => {
                // Ignore errors from cleanup
              })
            }
          >
            Open Gallery
          </button>
        )
      }

      const {container} = render(
        <ImagePickerProvider>
          <TestComponent />
        </ImagePickerProvider>
      )

      const galleryInput = container.querySelector(
        'input[type="file"]:not([capture])'
      ) as HTMLInputElement

      const button = screen.getByText('Open Gallery')
      fireEvent.click(button)

      // Set file and trigger change
      const file = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
      Object.defineProperty(galleryInput, 'files', {
        value: [file],
        configurable: true,
      })

      await act(async () => {
        fireEvent.change(galleryInput)
      })

      // Check that input value was cleared
      expect(galleryInput.value).toBe('')
    })
  })
})
