import { describe, expect, test, vi, beforeEach } from 'vitest'
import { render, within, fireEvent } from '@testing-library/react'
import { GeolocationControls } from './geolocation-controls'

describe('GeolocationControls', () => {
  const mockOnChange = vi.fn()
  const mockOnError = vi.fn()

  const mockGeolocation = {
    getCurrentPosition: vi.fn(),
    watchPosition: vi.fn(),
    clearWatch: vi.fn(),
  }

  const mockPermissions = {
    query: vi.fn(),
    state: 'granted' as const,
    onchange: null as (() => void) | null,
  }

  const mockNavigator = {
    geolocation: mockGeolocation,
    permissions: mockPermissions,
  }

  beforeEach(() => {
    vi.clearAllMocks()
    // Mock navigator
    Object.defineProperty(global, 'navigator', {
      value: mockNavigator,
      writable: true,
    })
  })

  test('renders correctly with geolocation enabled', () => {
    mockPermissions.query.mockResolvedValue(mockPermissions)
    const { container } = render(
      <GeolocationControls onChange={mockOnChange} onError={mockOnError} />,
    )

    const button = within(container).getByRole('button')
    const buttonElement = button as unknown as HTMLButtonElement
    expect(buttonElement.disabled).toBe(false)
  })

  test('handles geolocation permission denied', () => {
    mockPermissions.query.mockResolvedValue({
      ...mockPermissions,
      state: 'denied',
    })
    const { container } = render(
      <GeolocationControls onChange={mockOnChange} onError={mockOnError} />,
    )

    const button = within(container).getByRole('button')
    const buttonElement = button as unknown as HTMLButtonElement
    expect(buttonElement.disabled).toBe(false)
  })

  test('calls getCurrentPosition when clicked', () => {
    mockPermissions.query.mockResolvedValue(mockPermissions)

    const mockPosition = {
      coords: {
        latitude: 40.7128,
        longitude: -74.006,
      },
    }

    mockGeolocation.getCurrentPosition.mockImplementation(
      (success: PositionCallback) =>
        success(mockPosition as GeolocationPosition),
    )

    const { container } = render(
      <GeolocationControls onChange={mockOnChange} onError={mockOnError} />,
    )

    const button = within(container).getByRole('button')
    fireEvent.click(button)

    expect(mockGeolocation.getCurrentPosition).toHaveBeenCalled()
    expect(mockOnChange).toHaveBeenCalledWith(mockPosition.coords)
  })

  test('calls watchPosition when watch prop is true', () => {
    mockPermissions.query.mockResolvedValue(mockPermissions)
    const mockPosition = {
      coords: {
        latitude: 40.7128,
        longitude: -74.006,
      },
    }
    mockGeolocation.watchPosition.mockImplementation(
      (success: PositionCallback) =>
        success(mockPosition as GeolocationPosition),
    )

    const { container } = render(
      <GeolocationControls
        onChange={mockOnChange}
        onError={mockOnError}
        watch
      />,
    )

    const button = within(container).getByRole('button')
    fireEvent.click(button)

    expect(mockGeolocation.watchPosition).toHaveBeenCalled()
    expect(mockOnChange).toHaveBeenCalledWith(mockPosition.coords)
  })

  test('does not re-query permissions when only the onError identity changes', () => {
    mockPermissions.query.mockResolvedValue(mockPermissions)

    const { rerender } = render(
      <GeolocationControls onChange={mockOnChange} onError={vi.fn()} />,
    )
    expect(mockPermissions.query).toHaveBeenCalledTimes(1)

    // `onError` is read through an Effect Event, so the permission-query
    // effect depends on `hasGeolocation` alone. A fresh callback identity
    // must NOT re-run the query or re-register the permission listener.
    rerender(<GeolocationControls onChange={mockOnChange} onError={vi.fn()} />)
    expect(mockPermissions.query).toHaveBeenCalledTimes(1)
  })

  test('handles geolocation errors', () => {
    mockPermissions.query.mockResolvedValue(mockPermissions)

    const mockError = new Error('Geolocation error')
    mockGeolocation.getCurrentPosition.mockImplementation(
      (_, error: PositionErrorCallback) => {
        error(mockError as unknown as GeolocationPositionError)
      },
    )

    const { container } = render(
      <GeolocationControls onChange={mockOnChange} onError={mockOnError} />,
    )

    const button = within(container).getByRole('button')
    fireEvent.click(button)

    expect(mockOnError).toHaveBeenCalledWith(mockError)
  })
})
