import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
  getCurrentGcj02Location,
  isLocationPermissionDenied,
  isLocationSnapshotFresh,
} from '../src/services/location'

describe('current location adapter', () => {
  beforeEach(() => {
    vi.useRealTimers()
    Object.assign(uni, {
      canIUse: vi.fn(() => true),
      getLocation: vi.fn(),
    })
  })

  it('requests a new high-accuracy GCJ-02 sample', async () => {
    vi.mocked(uni.getLocation).mockImplementation((options) => {
      options.success?.({ longitude: 113.94, latitude: 22.76, accuracy: 18 } as UniNamespace.GetLocationSuccess)
      return undefined as never
    })

    await expect(getCurrentGcj02Location()).resolves.toMatchObject({
      longitude: 113.94,
      latitude: 22.76,
      accuracy: 18,
      receivedAt: expect.any(String),
      coordinateSystem: 'GCJ02',
    })
    expect(uni.getLocation).toHaveBeenCalledWith(expect.objectContaining({
      type: 'gcj02',
      isHighAccuracy: true,
      highAccuracyExpireTime: 8000,
    }))
  })

  it('starts a separate native request for every location lookup', async () => {
    vi.mocked(uni.getLocation).mockImplementation((options) => {
      options.success?.({ longitude: 113.94, latitude: 22.76, accuracy: 18 } as UniNamespace.GetLocationSuccess)
      return undefined as never
    })

    await getCurrentGcj02Location()
    await getCurrentGcj02Location()
    expect(uni.getLocation).toHaveBeenCalledTimes(2)
  })

  it('falls back to the basic object shape on older base libraries', async () => {
    vi.mocked(uni.canIUse).mockReturnValue(false)
    vi.mocked(uni.getLocation).mockImplementation((options) => {
      options.success?.({ longitude: 113.94, latitude: 22.76, accuracy: 20 } as UniNamespace.GetLocationSuccess)
      return undefined as never
    })

    await getCurrentGcj02Location()
    expect(uni.getLocation).toHaveBeenCalledWith(expect.not.objectContaining({ isHighAccuracy: true }))
  })

  it('times out once and ignores a late callback', async () => {
    vi.useFakeTimers()
    let success: ((result: UniNamespace.GetLocationSuccess) => void) | undefined
    vi.mocked(uni.getLocation).mockImplementation((options) => {
      success = options.success
      return undefined as never
    })

    const request = getCurrentGcj02Location({ timeoutMs: 3000 })
    const rejection = expect(request).rejects.toThrow('定位超时')
    await vi.advanceTimersByTimeAsync(3001)
    await rejection
    success?.({ longitude: 113.94, latitude: 22.76, accuracy: 20 } as UniNamespace.GetLocationSuccess)
    await vi.runAllTimersAsync()
  })

  it('identifies WeChat permission denials', () => {
    expect(isLocationPermissionDenied({ errMsg: 'getLocation:fail auth deny' })).toBe(true)
    expect(isLocationPermissionDenied({ errMsg: 'getLocation:fail privacy permission is not authorized' })).toBe(false)
    expect(isLocationPermissionDenied({ errMsg: 'getLocation:fail system error' })).toBe(false)
  })

  it('evaluates location freshness at exact boundaries and rejects invalid clocks', () => {
    const capturedAt = '2026-07-31T02:00:00.000Z'
    const capturedTimestamp = Date.parse(capturedAt)
    const maxAgeMs = 5 * 60 * 1000

    expect(isLocationSnapshotFresh(capturedAt, capturedTimestamp, maxAgeMs)).toBe(true)
    expect(isLocationSnapshotFresh(capturedAt, capturedTimestamp + maxAgeMs, maxAgeMs)).toBe(true)
    expect(isLocationSnapshotFresh(capturedAt, capturedTimestamp + maxAgeMs + 1, maxAgeMs)).toBe(false)
    expect(isLocationSnapshotFresh(capturedAt, capturedTimestamp - 1, maxAgeMs)).toBe(false)
    expect(isLocationSnapshotFresh('invalid', capturedTimestamp, maxAgeMs)).toBe(false)
  })
})
