/**
 * HomePage tests — M2-T09.
 *
 * Leaflet is mocked so jsdom doesn't choke on DOM APIs it doesn't support.
 * We verify:
 *  1. Controls render (time range inputs, layer toggles, apply button).
 *  2. Point-select: when onSelectLocation is called, EditLocationModal opens.
 *  3. Point-select: when onSelectPoo is called, EditPooModal opens.
 *  4. The map component is rendered (mocked).
 */

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { MantineProvider } from '@mantine/core'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { MemoryRouter } from 'react-router-dom'
import type { ReactNode } from 'react'

// ---------------------------------------------------------------------------
// Mock leaflet / react-leaflet before any component imports them.
// ---------------------------------------------------------------------------

vi.mock('leaflet', () => ({
  default: {},
  Icon: { Default: { prototype: {}, mergeOptions: vi.fn() } },
  DivIcon: vi.fn(() => ({})),
  heatLayer: vi.fn(() => ({ setLatLngs: vi.fn(), addTo: vi.fn() })),
  markerClusterGroup: vi.fn(() => ({ addLayer: vi.fn(), clearLayers: vi.fn() })),
  marker: vi.fn(() => ({
    bindTooltip: vi.fn().mockReturnThis(),
    on: vi.fn().mockReturnThis(),
  })),
  tileLayer: vi.fn(),
  map: vi.fn(),
}))

vi.mock('leaflet.heat', () => ({}))
vi.mock('leaflet.markercluster', () => ({}))

vi.mock('react-leaflet', () => ({
  MapContainer: ({ children }: { children: ReactNode }) => (
    <div data-testid="records-map">{children}</div>
  ),
  TileLayer: () => null,
  useMap: () => ({
    addLayer: vi.fn(),
    removeLayer: vi.fn(),
    hasLayer: vi.fn(() => false),
  }),
}))

// Mock leaflet image imports
vi.mock('leaflet/dist/images/marker-icon-2x.png', () => ({ default: 'marker-icon-2x.png' }))
vi.mock('leaflet/dist/images/marker-icon.png', () => ({ default: 'marker-icon.png' }))
vi.mock('leaflet/dist/images/marker-shadow.png', () => ({ default: 'marker-shadow.png' }))

// Mock leaflet CSS
vi.mock('leaflet/dist/leaflet.css', () => ({}))
vi.mock('leaflet.markercluster/dist/MarkerCluster.css', () => ({}))
vi.mock('leaflet.markercluster/dist/MarkerCluster.Default.css', () => ({}))

// ---------------------------------------------------------------------------
// Mock RecordsMap to capture onSelectLocation / onSelectPoo callbacks
// ---------------------------------------------------------------------------

import type { RecordsMapProps } from '../map/RecordsMap'

let capturedOnSelectLocation: RecordsMapProps['onSelectLocation'] | undefined
let capturedOnSelectPoo: RecordsMapProps['onSelectPoo'] | undefined

vi.mock('../map/RecordsMap', () => ({
  RecordsMap: (props: RecordsMapProps) => {
    capturedOnSelectLocation = props.onSelectLocation
    capturedOnSelectPoo = props.onSelectPoo
    return <div data-testid="records-map-mock" />
  },
}))

// ---------------------------------------------------------------------------
// Mock apiClient — return minimal data so queries resolve
// ---------------------------------------------------------------------------

vi.mock('../api/client', () => ({
  default: {
    GET: vi.fn(async (path: string) => {
      if (path === '/api/locations') {
        return {
          data: {
            items: [
              {
                person: 'alice',
                datetime: '2026-01-15T10:00:00Z',
                latitude: 39.9,
                longitude: 116.4,
                altitude: null,
              },
            ],
            limit: 5000,
            offset: 0,
          },
        }
      }
      if (path === '/api/poo') {
        return {
          data: {
            items: [
              {
                timestamp: '2026-01-20T09:00:00Z',
                status: 'done',
                latitude: 39.91,
                longitude: 116.41,
              },
            ],
            limit: 1000,
            offset: 0,
          },
        }
      }
      return { data: null }
    }),
  },
}))

// ---------------------------------------------------------------------------
// Now import components under test (after mocks are registered)
// ---------------------------------------------------------------------------

import { HomePage } from './HomePage'

// ---------------------------------------------------------------------------
// Test wrapper
// ---------------------------------------------------------------------------

function makeQC() {
  return new QueryClient({ defaultOptions: { queries: { retry: false } } })
}

function Wrapper({ qc, children }: { qc: QueryClient; children: ReactNode }) {
  return (
    <MantineProvider>
      <QueryClientProvider client={qc}>
        <MemoryRouter>{children}</MemoryRouter>
      </QueryClientProvider>
    </MantineProvider>
  )
}

// Helper: render HomePage and wait for queries to resolve
async function renderHomePage() {
  const qc = makeQC()
  const utils = render(
    <Wrapper qc={qc}>
      <HomePage />
    </Wrapper>,
  )
  // Wait for the map mock to appear (data loaded)
  await waitFor(() => screen.getByTestId('records-map-mock'))
  return utils
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe('HomePage', () => {
  beforeEach(() => {
    capturedOnSelectLocation = undefined
    capturedOnSelectPoo = undefined
  })

  it('renders time-range controls', async () => {
    await renderHomePage()
    expect(screen.getByTestId('time-start-input')).toBeTruthy()
    expect(screen.getByTestId('time-end-input')).toBeTruthy()
    expect(screen.getByTestId('apply-window-button')).toBeTruthy()
  })

  it('renders layer toggle switches', async () => {
    await renderHomePage()
    expect(screen.getByTestId('toggle-location-heat')).toBeTruthy()
    expect(screen.getByTestId('toggle-poo-heat')).toBeTruthy()
    expect(screen.getByTestId('toggle-scatter')).toBeTruthy()
  })

  it('renders the RecordsMap component', async () => {
    await renderHomePage()
    expect(screen.getByTestId('records-map-mock')).toBeTruthy()
  })

  it('opens EditLocationModal when onSelectLocation is called with a location record', async () => {
    await renderHomePage()

    // Simulate clicking a location scatter point
    const record = {
      person: 'alice',
      datetime: '2026-01-15T10:00:00Z',
      latitude: 39.9,
      longitude: 116.4,
      altitude: null,
    }
    expect(capturedOnSelectLocation).toBeDefined()
    capturedOnSelectLocation!(record)

    // EditLocationModal should appear
    await waitFor(() => screen.getByTestId('edit-location-modal'))
    expect(screen.getByTestId('edit-location-modal')).toBeTruthy()
  })

  it('opens EditPooModal when onSelectPoo is called with a poo record', async () => {
    await renderHomePage()

    const record = {
      timestamp: '2026-01-20T09:00:00Z',
      status: 'done',
      latitude: 39.91,
      longitude: 116.41,
    }
    expect(capturedOnSelectPoo).toBeDefined()
    capturedOnSelectPoo!(record)

    await waitFor(() => screen.getByTestId('edit-poo-modal'))
    expect(screen.getByTestId('edit-poo-modal')).toBeTruthy()
  })

  it('closes EditLocationModal when Cancel is clicked', async () => {
    await renderHomePage()

    const record = {
      person: 'alice',
      datetime: '2026-01-15T10:00:00Z',
      latitude: 39.9,
      longitude: 116.4,
      altitude: null,
    }
    capturedOnSelectLocation!(record)
    await waitFor(() => screen.getByTestId('edit-location-modal'))

    fireEvent.click(screen.getByTestId('edit-location-cancel'))
    await waitFor(() => expect(screen.queryByTestId('edit-location-modal')).toBeNull())
  })

  it('closes EditPooModal when Cancel is clicked', async () => {
    await renderHomePage()

    const record = {
      timestamp: '2026-01-20T09:00:00Z',
      status: 'done',
      latitude: 39.91,
      longitude: 116.41,
    }
    capturedOnSelectPoo!(record)
    await waitFor(() => screen.getByTestId('edit-poo-modal'))

    fireEvent.click(screen.getByTestId('edit-poo-cancel'))
    await waitFor(() => expect(screen.queryByTestId('edit-poo-modal')).toBeNull())
  })

  it('time-range inputs have default values', async () => {
    await renderHomePage()
    const startInput = screen.getByTestId('time-start-input') as HTMLInputElement
    const endInput = screen.getByTestId('time-end-input') as HTMLInputElement
    expect(startInput.value).toBeTruthy()
    expect(endInput.value).toBeTruthy()
  })

  it('Apply button re-triggers data fetch with new window', async () => {
    await renderHomePage()
    const startInput = screen.getByTestId('time-start-input') as HTMLInputElement
    fireEvent.change(startInput, { target: { value: '2026-01-01T00:00' } })
    fireEvent.click(screen.getByTestId('apply-window-button'))
    // Just verify no crash; data refresh happens async via React Query.
    await waitFor(() => screen.getByTestId('records-map-mock'))
  })
})
