import { describe, it, expect, vi } from 'vitest'
import { CustomMeasureBufferMode } from './custom-measurement-buffer.class'
import { MapMeasurementUnitAuto } from '../const'
import type {
  ModeProps,
  FeatureCollection,
} from '@deck.gl-community/editable-layers'
import type {
  BasePointerEvent,
  PointerMoveEvent,
} from 'node_modules/@deck.gl-community/editable-layers/dist/edit-modes/types'

describe('CustomMeasureBufferMode', () => {
  const mockLastPointerMoveEvent: PointerMoveEvent = {
    picks: [],
    screenCoords: [0, 0],
    mapCoords: [0, 0],
    sourceEvent: null,
    cancelPan: vi.fn(),
    pointerDownPicks: null,
    pointerDownScreenCoords: null,
    pointerDownMapCoords: null,
    isDragging: false,
  }

  const mockProps: ModeProps<FeatureCollection> = {
    data: {
      type: 'FeatureCollection',
      features: [],
    },
    onEdit: vi.fn(),
    onUpdateCursor: vi.fn(),
    selectedIndexes: [],
    lastPointerMoveEvent: mockLastPointerMoveEvent,
    modeConfig: {},
    cursor: null,
  }

  describe('constructor', () => {
    it('should initialize with default values', () => {
      const mode = new CustomMeasureBufferMode()
      expect(mode._isMeasuringSessionFinished).toBe(false)
      expect(mode._currentArea).toBe(0)
      expect(mode.tooltips).toEqual([])
      expect(mode.resultPolygon).toEqual({
        type: 'FeatureCollection',
        features: [],
      })
    })
  })

  describe('handleClick', () => {
    it('should handle first click to set center', () => {
      const mode = new CustomMeasureBufferMode()
      const mockEvent: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockEvent, mockProps)

      // First click sets the center, click sequence should have been updated
      const clickSequence = mode.getClickSequence()
      expect(clickSequence.length).toBeGreaterThan(0)
    })

    it('should restart measurement session when finished', () => {
      const mode = new CustomMeasureBufferMode()
      mode._isMeasuringSessionFinished = true
      mode._currentArea = 100

      const mockEvent: BasePointerEvent = {
        mapCoords: [1, 1],
        screenCoords: [100, 100],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockEvent, mockProps)

      expect(mode._isMeasuringSessionFinished).toBe(false)
      expect(mode._currentArea).toBe(0)
    })

    it('should reset state on restart', () => {
      const mode = new CustomMeasureBufferMode()
      mode._isMeasuringSessionFinished = true
      mode._currentArea = 500
      mode.tooltips = [{ position: [0, 0], text: 'test' }]

      const mockEvent: BasePointerEvent = {
        mapCoords: [2, 2],
        screenCoords: [200, 200],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockEvent, mockProps)

      // After restart, values should be reset
      const clickSequence = mode.getClickSequence()
      expect(clickSequence.length).toBeGreaterThan(0)
    })
  })

  describe('handlePointerMove', () => {
    it('should return early when measurement session is finished', () => {
      const mode = new CustomMeasureBufferMode()
      mode._isMeasuringSessionFinished = true

      const mockEvent: PointerMoveEvent = {
        picks: [],
        screenCoords: [50, 50],
        mapCoords: [0.5, 0.5],
        sourceEvent: null,
        cancelPan: vi.fn(),
        pointerDownPicks: null,
        pointerDownScreenCoords: null,
        pointerDownMapCoords: null,
        isDragging: false,
      }

      const initialTooltips = [...mode.tooltips]
      mode.handlePointerMove(mockEvent, mockProps)

      expect(mode.tooltips).toEqual(initialTooltips)
    })

    it('should handle pointer move during active session', () => {
      const mode = new CustomMeasureBufferMode()

      // Start a measurement by clicking center
      const mockClick: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClick, mockProps)

      const mockEvent: PointerMoveEvent = {
        picks: [],
        screenCoords: [50, 50],
        mapCoords: [0.5, 0.5],
        sourceEvent: null,
        cancelPan: vi.fn(),
        pointerDownPicks: null,
        pointerDownScreenCoords: null,
        pointerDownMapCoords: null,
        isDragging: false,
      }

      mode.handlePointerMove(mockEvent, mockProps)

      // Should update tooltips
      expect(mode._isMeasuringSessionFinished).toBe(false)
    })
  })

  describe('handleKeyUp', () => {
    it('should finalize measurement on Enter', () => {
      const mode = new CustomMeasureBufferMode()

      // Start a buffer by clicking center
      const mockClick: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClick, mockProps)
      vi.clearAllMocks()

      const mockKeyEvent = new KeyboardEvent('keyup', { key: 'Enter' })
      mode.handleKeyUp(mockKeyEvent, mockProps)

      // Should update cursor to cell after finalization
      expect(mockProps.onUpdateCursor).toHaveBeenCalledWith('cell')
    })

    it('should reset measurement on Escape', () => {
      const mode = new CustomMeasureBufferMode()

      const mockClick: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClick, mockProps)
      vi.clearAllMocks()

      const mockKeyEvent = new KeyboardEvent('keyup', { key: 'Escape' })
      mode.handleKeyUp(mockKeyEvent, mockProps)

      expect(mockProps.onEdit).toHaveBeenCalledWith(
        expect.objectContaining({
          editType: 'cancelFeature',
          editContext: {},
        }),
      )
    })
  })

  describe('checkAndFinishPolygon', () => {
    it('should check finalization conditions', () => {
      const mode = new CustomMeasureBufferMode()

      const mockClick: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClick, mockProps)

      // Call checkAndFinishPolygon
      mode.checkAndFinishPolygon(mockProps)

      // Should not throw and internal state should be consistent
      expect(mode._isMeasuringSessionFinished).toBeDefined()
    })
  })

  describe('isFinalizedEdition', () => {
    it('should return true when buffer has valid polygon', () => {
      const mode = new CustomMeasureBufferMode()
      const clickSequence = [
        [0, 0],
        [1, 0],
      ]
      const tentativeFeature = {
        geometry: { type: 'Polygon' as const, coordinates: [] },
      }

      const result = mode.isFinalizedEdition(clickSequence, tentativeFeature)

      expect(result).toBe(true)
    })

    it('should return false when not enough clicks', () => {
      const mode = new CustomMeasureBufferMode()
      const clickSequence = [[0, 0]]
      const tentativeFeature = {
        geometry: { type: 'Polygon' as const, coordinates: [] },
      }

      const result = mode.isFinalizedEdition(clickSequence, tentativeFeature)

      expect(result).toBe(false)
    })

    it('should return false when geometry is not a polygon', () => {
      const mode = new CustomMeasureBufferMode()
      const clickSequence = [
        [0, 0],
        [1, 0],
      ]
      const tentativeFeature = {
        geometry: { type: 'Point' as const, coordinates: [] },
      }

      const result = mode.isFinalizedEdition(clickSequence, tentativeFeature)

      expect(result).toBe(false)
    })
  })

  describe('resetClickSequence', () => {
    it('should reset all measurement state', () => {
      const mode = new CustomMeasureBufferMode()
      mode._currentArea = 100
      mode.tooltips = [{ position: [0, 0], text: 'test' }]
      mode._isMeasuringSessionFinished = true

      mode.resetClickSequence()

      expect(mode._currentArea).toBe(0)
      expect(mode.tooltips).toEqual([])
      expect(mode._isMeasuringSessionFinished).toBe(false)
      expect(mode.radius).toBe(0)
      expect(mode.areaCircle).toBe(0)
    })
  })

  describe('getGuides', () => {
    it('should return result polygon when measurement is finished', () => {
      const mode = new CustomMeasureBufferMode()
      mode._isMeasuringSessionFinished = true
      mode.resultPolygon = {
        type: 'FeatureCollection',
        features: [
          {
            type: 'Feature',
            geometry: { type: 'Polygon', coordinates: [] },
            properties: {},
          },
        ],
      }

      const guides = mode.getGuides(mockProps)

      expect(guides).toEqual(mode.resultPolygon)
    })

    it('should return super guides when measurement is not finished', () => {
      const mode = new CustomMeasureBufferMode()
      mode._isMeasuringSessionFinished = false

      const guides = mode.getGuides(mockProps)

      expect(guides).toBeDefined()
    })
  })

  describe('getTooltips', () => {
    it('should return current tooltips', () => {
      const mode = new CustomMeasureBufferMode()
      const mockTooltips = [{ position: [0, 0], text: 'test' }]
      mode.tooltips = mockTooltips

      const tooltips = mode.getTooltips()

      expect(tooltips).toEqual(mockTooltips)
    })

    it('should return empty array when no tooltips', () => {
      const mode = new CustomMeasureBufferMode()
      mode.tooltips = []

      const tooltips = mode.getTooltips()

      expect(tooltips).toEqual([])
    })
  })

  describe('resetEdition', () => {
    it('should reset measurement and call onEdit', () => {
      const mode = new CustomMeasureBufferMode()
      mode._currentArea = 100

      mode.resetEdition(mockProps)

      expect(mockProps.onEdit).toHaveBeenCalledWith({
        updatedData: mockProps.data,
        editType: 'cancelFeature',
        editContext: {},
      })
    })
  })

  describe('finalizeEdition', () => {
    it('should complete the buffer and update state', () => {
      const mode = new CustomMeasureBufferMode()

      // Click to start buffer
      const mockClick: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClick, mockProps)
      vi.clearAllMocks()

      mode.finalizeEdition(mockProps)

      expect(mode._isMeasuringSessionFinished).toBe(true)
      expect(mockProps.onUpdateCursor).toHaveBeenCalledWith('cell')
    })

    it('should call onEdit with correct context', () => {
      const mode = new CustomMeasureBufferMode()

      const mockClick: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClick, mockProps)

      mode.finalizeEdition(mockProps)

      expect(mode._isMeasuringSessionFinished).toBe(true)
    })
  })

  describe('getTooltipText', () => {
    it('should format area text for metric units', () => {
      const mode = new CustomMeasureBufferMode()

      const result = mode.getTooltipText({
        meters: 1500000,
        locale: 'en-US',
        unit: MapMeasurementUnitAuto['m-km'],
      })

      expect(result).toBeDefined()
      expect(typeof result).toBe('string')
    })

    it('should format area text for imperial units', () => {
      const mode = new CustomMeasureBufferMode()

      const result = mode.getTooltipText({
        meters: 1000000,
        locale: 'en-US',
        unit: MapMeasurementUnitAuto['ft-mi'],
      })

      expect(result).toBeDefined()
      expect(typeof result).toBe('string')
    })

    it('should respect custom text options', () => {
      const mode = new CustomMeasureBufferMode()

      const result = mode.getTooltipText({
        meters: 1234567.89,
        locale: 'en-US',
        unit: MapMeasurementUnitAuto['m-km'],
        options: {
          minimumFractionDigits: 0,
          maximumFractionDigits: 0,
        },
      })

      expect(result).toBeDefined()
      expect(typeof result).toBe('string')
    })

    it('should handle different locales', () => {
      const mode = new CustomMeasureBufferMode()

      const result = mode.getTooltipText({
        meters: 5000,
        locale: 'de-DE',
        unit: MapMeasurementUnitAuto['m-km'],
      })

      expect(result).toBeDefined()
      expect(typeof result).toBe('string')
    })
  })

  describe('edge cases', () => {
    it('should handle multiple restarts', () => {
      const mode = new CustomMeasureBufferMode()

      const mockClick1: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      // First session
      mode.handleClick(mockClick1, mockProps)
      mode.finalizeEdition(mockProps)
      expect(mode._isMeasuringSessionFinished).toBe(true)

      // Restart session
      const mockClick2: BasePointerEvent = {
        mapCoords: [2, 2],
        screenCoords: [200, 200],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClick2, mockProps)
      expect(mode._isMeasuringSessionFinished).toBe(false)
    })

    it('should handle escape after finalization', () => {
      const mode = new CustomMeasureBufferMode()

      const mockClick: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClick, mockProps)
      mode.finalizeEdition(mockProps)

      vi.clearAllMocks()

      const mockKeyEvent = new KeyboardEvent('keyup', { key: 'Escape' })
      mode.handleKeyUp(mockKeyEvent, mockProps)

      expect(mockProps.onEdit).toHaveBeenCalled()
    })
  })
})
