import { type LocationSample } from './locationSource'

export const DEFAULT_MOCK_LOCATION_BRIDGE_URL = 'wss://gs.gotomars.xyz/mock-gps'

export interface MockLocationBridgeCallbacks {
  onOpen: () => void
  onClose: (message: string) => void
  onError: (message: string) => void
  onSample: (sample: LocationSample) => void
}

type RawMockGpsMessage = {
  type?: string
  timestamp?: number
  lat?: number
  lon?: number
  accuracyMeters?: number
  speedMps?: number
  headingDeg?: number
  channelId?: string
}

function normalizeMockChannelId(rawChannelId: string | null | undefined): string {
  const trimmed = String(rawChannelId || '').trim()
  return trimmed || 'default'
}

function safeParseMessage(data: string): RawMockGpsMessage | null {
  try {
    return JSON.parse(data) as RawMockGpsMessage
  } catch (_error) {
    return null
  }
}

function toLocationSample(message: RawMockGpsMessage, expectedChannelId: string): LocationSample | null {
  if (message.type !== 'mock_gps') {
    return null
  }

  if (!Number.isFinite(message.lat) || !Number.isFinite(message.lon)) {
    return null
  }

  if (normalizeMockChannelId(message.channelId) !== expectedChannelId) {
    return null
  }

  return {
    latitude: Number(message.lat),
    longitude: Number(message.lon),
    accuracy: Number.isFinite(message.accuracyMeters) ? Number(message.accuracyMeters) : undefined,
    speed: Number.isFinite(message.speedMps) ? Number(message.speedMps) : null,
    headingDeg: Number.isFinite(message.headingDeg) ? Number(message.headingDeg) : null,
    timestamp: Number.isFinite(message.timestamp) ? Number(message.timestamp) : Date.now(),
    sourceMode: 'mock',
  }
}

export class MockLocationBridge {
  callbacks: MockLocationBridgeCallbacks
  socketTask: WechatMiniprogram.SocketTask | null
  connected: boolean
  connecting: boolean
  url: string
  channelId: string

  constructor(callbacks: MockLocationBridgeCallbacks) {
    this.callbacks = callbacks
    this.socketTask = null
    this.connected = false
    this.connecting = false
    this.url = DEFAULT_MOCK_LOCATION_BRIDGE_URL
    this.channelId = 'default'
  }

  setChannelId(channelId: string): void {
    this.channelId = normalizeMockChannelId(channelId)
  }

  connect(url = DEFAULT_MOCK_LOCATION_BRIDGE_URL): void {
    if (this.connected || this.connecting) {
      return
    }

    this.url = url
    this.connecting = true

    try {
      const socketTask = wx.connectSocket({
        url,
      })
      this.socketTask = socketTask

      socketTask.onOpen(() => {
        this.connected = true
        this.connecting = false
        this.callbacks.onOpen()
      })

      socketTask.onClose((result) => {
        const reason = result && result.reason ? result.reason : '模拟源连接已关闭'
        this.connected = false
        this.connecting = false
        this.socketTask = null
        this.callbacks.onClose(reason)
      })

      socketTask.onError((result) => {
        const message = result && result.errMsg ? result.errMsg : '模拟源连接失败'
        this.connected = false
        this.connecting = false
        this.socketTask = null
        this.callbacks.onError(message)
      })

      socketTask.onMessage((result) => {
        if (!result || typeof result.data !== 'string') {
          return
        }

        const parsed = safeParseMessage(result.data)
        if (!parsed) {
          this.callbacks.onError('模拟源消息不是合法 JSON')
          return
        }

        const sample = toLocationSample(parsed, this.channelId)
        if (!sample) {
          return
        }

        this.callbacks.onSample(sample)
      })
    } catch (error) {
      this.connected = false
      this.connecting = false
      this.socketTask = null
      const message = error && (error as Error).message ? (error as Error).message : '模拟源连接创建失败'
      this.callbacks.onError(message)
    }
  }

  disconnect(): void {
    if (!this.socketTask) {
      if (this.connected || this.connecting) {
        this.connected = false
        this.connecting = false
      }
      return
    }

    const socketTask = this.socketTask
    this.socketTask = null
    this.connected = false
    this.connecting = false
    socketTask.close({})
  }

  destroy(): void {
    this.disconnect()
  }
}
