import EventEmitter from 'events'
import type TypedEmitter from 'typed-emitter'

import { Checker, CheckerOptions, CheckInfo, CheckStatus, InstantiableCheck } from './checks/Checker'
import { PublishAudioCheck } from './checks/publishAudio'
import { PublishVideoCheck } from './checks/publishVideo'
import { ReconnectCheck } from './checks/reconnect'
import { TURNCheck } from './checks/turn'
import { WebRTCCheck } from './checks/webrtc'
import { WebSocketCheck } from './checks/websocket'
import { TrickleCheck } from './checks/trickle'
import { MediaDevicesCheck } from './checks/mediaDevices'

export type { CheckInfo, CheckStatus }

export class ConnectionCheck extends (EventEmitter as new () => TypedEmitter<ConnectionCheckCallbacks>) {
  token: string
  url: string
  private checkResults: Map<number, CheckInfo> = new Map()

  constructor(url: string, token: string) {
    super()
    this.url = url
    this.token = token
  }

  private getNextCheckId() {
    const nextId = this.checkResults.size
    this.checkResults.set(nextId, {
      logs: [],
      status: CheckStatus.IDLE,
      name: '',
      description: '',
    })
    return nextId
  }

  private updateCheck(checkId: number, info: CheckInfo) {
    this.checkResults.set(checkId, info)
    this.emit('checkUpdate', checkId, info)
  }

  isSuccess() {
    return Array.from(this.checkResults.values()).every((r) => r.status !== CheckStatus.FAILED)
  }

  getResults() {
    return Array.from(this.checkResults.values())
  }

  async createAndRunCheck<T extends Checker>(check: InstantiableCheck<T>, additionalParams?: CheckerOptions) {
    const checkId = this.getNextCheckId()
    const test = new check(this.url, this.token, additionalParams)
    const handleUpdate = (info: CheckInfo) => {
      this.updateCheck(checkId, info)
    }
    test.on('update', handleUpdate)
    const result = await test.run()
    test.off('update', handleUpdate)
    return result
  }

  async checkWebsocket() {
    return this.createAndRunCheck(WebSocketCheck)
  }

  async checkWebRTC(turnServers: Array<RTCIceServer>) {
    return this.createAndRunCheck(WebRTCCheck, { connectOptions: { rtcConfig: { iceServers: turnServers } } })
  }

  async checkTURN(turnServers: Array<RTCIceServer>) {
    return this.createAndRunCheck(TURNCheck, { connectOptions: { rtcConfig: { iceServers: turnServers } } })
  }

  async checkReconnect(turnServers: Array<RTCIceServer>) {
    return this.createAndRunCheck(ReconnectCheck, { connectOptions: { rtcConfig: { iceServers: turnServers } } })
  }

  async checkPublishAudio(turnServers: Array<RTCIceServer>) {
    return this.createAndRunCheck(PublishAudioCheck, { connectOptions: { rtcConfig: { iceServers: turnServers } } })
  }

  async checkPublishVideo(turnServers: Array<RTCIceServer>) {
    return this.createAndRunCheck(PublishVideoCheck, { connectOptions: { rtcConfig: { iceServers: turnServers } } })
  }

  async checkTrickle(turnServers: Array<RTCIceServer>) {
    return this.createAndRunCheck(TrickleCheck, { connectOptions: { rtcConfig: { iceServers: turnServers } } })
  }

  async checkMediaDevices(turnServers: Array<RTCIceServer>) {
    return this.createAndRunCheck(MediaDevicesCheck, { connectOptions: { rtcConfig: { iceServers: turnServers } } })
  }
}

type ConnectionCheckCallbacks = {
  checkUpdate: (id: number, info: CheckInfo) => void
}
