const loading = ref(false)
const statusLoading = ref(false)
const websocketTesting = ref(false)
const callHistoryLoading = ref(false)
const config = ref(null)
const status = ref(null)
const websocketResult = ref(null)
const callHistory = ref([])
const lastUpdated = ref(null)
const selectedExtension = ref("1001")
const dialTarget = ref("600")
const sipModule = ref(null)
const userAgent = shallowRef(null)
const registerer = shallowRef(null)
const activeSession = shallowRef(null)
const remoteAudio = ref(null)
const sipLoading = ref(false)
const sipRegistered = ref(false)
const sipStatus = ref("Nicht verbunden")
const sipError = ref(null)
const incomingCall = ref(null)
const callState = ref("idle")
const registererState = ref("Initial")
const sipEvents = ref([])
const ringtoneTimer = shallowRef(null)
const ringtoneAudioContext = shallowRef(null)
const callSignalStatus = ref("Bereit")
const originalDocumentTitle = ref("")
const activeCallRecordId = ref(null)
const activeCallDirection = ref(null)
const activeCallAnsweredAt = ref(null)

export const useTelephonySoftphone = () => {
  const toast = useToast()
  const { $api } = useNuxtApp()

  const incomingCaller = computed(() => {
    const identity = incomingCall.value?.remoteIdentity
    return identity?.displayName || identity?.uri?.user || "Unbekannter Anrufer"
  })

  const selectedAccount = computed(() =>
    (config.value?.accounts || []).find((account) => account.extension === selectedExtension.value)
      || (config.value?.accounts || [])[0]
  )

  const canRegisterSip = computed(() =>
    Boolean(config.value?.sipWebSocketUrl && config.value?.sipDomain && selectedAccount.value && !sipLoading.value)
  )

  const canStartCall = computed(() =>
    Boolean(sipRegistered.value && dialTarget.value?.trim() && !activeSession.value && !sipLoading.value)
  )

  const canHangup = computed(() =>
    Boolean(activeSession.value && callState.value !== "terminated")
  )

  const statusColor = computed(() => {
    if (status.value?.reachable) return "success"
    if (!status.value?.enabled) return "warning"
    return "error"
  })

  const statusIcon = computed(() => {
    if (status.value?.reachable) return "i-heroicons-signal"
    if (!status.value?.enabled) return "i-heroicons-pause-circle"
    return "i-heroicons-signal-slash"
  })

  const websocketColor = computed(() => {
    if (!websocketResult.value) return "neutral"
    return websocketResult.value.ok ? "success" : "error"
  })

  const addSipEvent = (message) => {
    sipEvents.value = [
      {
        time: new Date().toLocaleTimeString("de-DE"),
        message,
      },
      ...sipEvents.value,
    ].slice(0, 8)
  }

  const sipCallIdFromSession = (session) =>
    session?.request?.callId
    || session?.request?.message?.callId
    || session?.incomingInviteRequest?.message?.callId
    || session?.outgoingInviteRequest?.message?.callId
    || null

  const loadCallHistory = async (filters = {}) => {
    callHistoryLoading.value = true

    try {
      callHistory.value = await $api("/api/telephony/calls", {
        params: {
          limit: 50,
          ...filters,
        }
      })
    } catch (error) {
      addSipEvent(`Anrufhistorie konnte nicht geladen werden: ${error?.message || "Unbekannter Fehler"}`)
    } finally {
      callHistoryLoading.value = false
    }
  }

  const createCallRecord = async ({ direction, status: nextStatus, localExtension, remoteNumber, remoteDisplayName, session }) => {
    try {
      const call = await $api("/api/telephony/calls", {
        method: "POST",
        body: {
          direction,
          status: nextStatus,
          localExtension,
          remoteNumber,
          remoteDisplayName,
          sipCallId: sipCallIdFromSession(session),
          startedAt: new Date().toISOString(),
        },
      })

      activeCallRecordId.value = call.id
      activeCallDirection.value = direction
      activeCallAnsweredAt.value = null
      await loadCallHistory()
      return call
    } catch (error) {
      addSipEvent(`Anrufhistorie konnte nicht geschrieben werden: ${error?.message || "Unbekannter Fehler"}`)
      return null
    }
  }

  const updateCallRecord = async (id, payload) => {
    if (!id) return null

    try {
      const call = await $api(`/api/telephony/calls/${id}`, {
        method: "PATCH",
        body: payload,
      })

      await loadCallHistory()
      return call
    } catch (error) {
      addSipEvent(`Anrufhistorie konnte nicht aktualisiert werden: ${error?.message || "Unbekannter Fehler"}`)
      return null
    }
  }

  const markCallAnswered = async () => {
    if (!activeCallRecordId.value || activeCallAnsweredAt.value) return

    activeCallAnsweredAt.value = new Date()
    await updateCallRecord(activeCallRecordId.value, {
      status: "active",
      answeredAt: activeCallAnsweredAt.value.toISOString(),
    })
  }

  const finalizeCallRecord = async (statusOverride = null) => {
    if (!activeCallRecordId.value) return

    const id = activeCallRecordId.value
    const direction = activeCallDirection.value
    const answeredAt = activeCallAnsweredAt.value
    const endedAt = new Date()
    const finalStatus = statusOverride
      || (answeredAt ? "completed" : direction === "incoming" ? "missed" : "canceled")

    activeCallRecordId.value = null
    activeCallDirection.value = null
    activeCallAnsweredAt.value = null

    await updateCallRecord(id, {
      status: finalStatus,
      endedAt: endedAt.toISOString(),
    })
  }

  const ensureSipModule = async () => {
    if (!sipModule.value) {
      sipModule.value = await import("sip.js")
    }

    return sipModule.value
  }

  const cleanupRemoteAudio = () => {
    if (!remoteAudio.value?.srcObject) return

    const stream = remoteAudio.value.srcObject
    if (stream?.getTracks) {
      stream.getTracks().forEach((track) => track.stop())
    }

    remoteAudio.value.srcObject = null
  }

  const attachRemoteAudio = async (session) => {
    const peerConnection = session?.sessionDescriptionHandler?.peerConnection
    if (!peerConnection || !remoteAudio.value) return

    const remoteStream = new MediaStream()
    peerConnection.getReceivers().forEach((receiver) => {
      if (receiver.track) remoteStream.addTrack(receiver.track)
    })

    remoteAudio.value.srcObject = remoteStream

    try {
      await remoteAudio.value.play()
    } catch (error) {
      addSipEvent(`Audioausgabe blockiert: ${error?.message || "Browser-Autoplay"}`)
    }
  }

  const playRingtoneTick = async () => {
    if (!window.AudioContext && !window.webkitAudioContext) return

    const AudioContextConstructor = window.AudioContext || window.webkitAudioContext
    const context = ringtoneAudioContext.value || new AudioContextConstructor()
    ringtoneAudioContext.value = context

    if (context.state === "suspended") {
      await context.resume()
    }

    const oscillator = context.createOscillator()
    const gain = context.createGain()
    oscillator.type = "sine"
    oscillator.frequency.value = 880
    gain.gain.setValueAtTime(0.0001, context.currentTime)
    gain.gain.exponentialRampToValueAtTime(0.12, context.currentTime + 0.03)
    gain.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + 0.35)
    oscillator.connect(gain)
    gain.connect(context.destination)
    oscillator.start()
    oscillator.stop(context.currentTime + 0.38)
  }

  const stopCallSignaling = () => {
    if (ringtoneTimer.value) {
      window.clearInterval(ringtoneTimer.value)
      ringtoneTimer.value = null
    }

    if (originalDocumentTitle.value) {
      document.title = originalDocumentTitle.value
      originalDocumentTitle.value = ""
    }

    if (navigator.vibrate) {
      navigator.vibrate(0)
    }

    callSignalStatus.value = "Bereit"
  }

  const startCallSignaling = async () => {
    stopCallSignaling()
    callSignalStatus.value = "Signalisiert"
    originalDocumentTitle.value = document.title
    document.title = "Eingehender Anruf - FEDEO"

    if (navigator.vibrate) {
      navigator.vibrate([180, 90, 180])
    }

    try {
      await playRingtoneTick()
      ringtoneTimer.value = window.setInterval(() => {
        playRingtoneTick().catch((error) => {
          callSignalStatus.value = "Klingelton blockiert"
          addSipEvent(`Klingelton blockiert: ${error?.message || "Browser-Autoplay"}`)
        })
      }, 1400)
    } catch (error) {
      callSignalStatus.value = "Klingelton blockiert"
      addSipEvent(`Klingelton blockiert: ${error?.message || "Browser-Autoplay"}`)
    }
  }

  const setupSession = (session, direction = "outgoing") => {
    const { SessionState } = sipModule.value

    activeSession.value = session
    callState.value = direction === "incoming" ? "incoming" : "connecting"
    sipStatus.value = direction === "incoming" ? "Eingehender Anruf" : "Anruf wird aufgebaut"

    session.stateChange.addListener(async (state) => {
      if (state === SessionState.Establishing) {
        callState.value = "connecting"
        sipStatus.value = "Anruf wird aufgebaut"
      }

      if (state === SessionState.Established) {
        stopCallSignaling()
        callState.value = "active"
        sipStatus.value = "Im Gespräch"
        incomingCall.value = null
        await markCallAnswered()
        await attachRemoteAudio(session)
      }

      if (state === SessionState.Terminated) {
        await finalizeCallRecord()
        stopCallSignaling()
        callState.value = "terminated"
        sipStatus.value = sipRegistered.value ? "Registriert" : "Nicht verbunden"
        activeSession.value = null
        incomingCall.value = null
        cleanupRemoteAudio()
      }
    })
  }

  const loadTelephony = async () => {
    loading.value = true
    try {
      const [configRes, statusRes] = await Promise.all([
        $api("/api/telephony/config"),
        $api("/api/telephony/status")
      ])

      config.value = configRes
      status.value = statusRes
      lastUpdated.value = new Date()

      const accounts = configRes?.accounts || []
      if (!selectedAccount.value && accounts.length) {
        selectedExtension.value = accounts[0].extension
      }

      await loadCallHistory()
    } catch (error) {
      toast.add({
        title: "Telefonie-Status konnte nicht geladen werden",
        color: "error"
      })
    } finally {
      loading.value = false
    }
  }

  const refreshStatus = async () => {
    statusLoading.value = true
    try {
      status.value = await $api("/api/telephony/status")
      lastUpdated.value = new Date()
    } catch (error) {
      toast.add({
        title: "Asterisk-Status konnte nicht geprüft werden",
        color: "error"
      })
    } finally {
      statusLoading.value = false
    }
  }

  const testWebSocket = async () => {
    if (!config.value?.sipWebSocketUrl || websocketTesting.value) return

    websocketTesting.value = true
    websocketResult.value = null

    await new Promise((resolve) => {
      let settled = false
      const socket = new WebSocket(config.value.sipWebSocketUrl, "sip")
      const timer = window.setTimeout(() => {
        if (settled) return
        settled = true
        socket.close()
        websocketResult.value = {
          ok: false,
          message: "WebSocket-Verbindung ist abgelaufen."
        }
        resolve()
      }, 3000)

      socket.onopen = () => {
        if (settled) return
        settled = true
        window.clearTimeout(timer)
        websocketResult.value = {
          ok: true,
          message: "SIP-WebSocket ist aus dem Browser erreichbar."
        }
        socket.close()
        resolve()
      }

      socket.onerror = () => {
        if (settled) return
        settled = true
        window.clearTimeout(timer)
        websocketResult.value = {
          ok: false,
          message: "SIP-WebSocket konnte nicht geöffnet werden."
        }
        resolve()
      }
    })

    websocketTesting.value = false
  }

  const stopSip = async () => {
    sipLoading.value = true
    sipError.value = null

    try {
      if (activeSession.value) {
        await hangupCall()
      }

      if (registerer.value) {
        await registerer.value.unregister()
        registerer.value = null
      }

      if (userAgent.value) {
        await userAgent.value.stop()
        userAgent.value = null
      }

      sipRegistered.value = false
      registererState.value = "Initial"
      sipStatus.value = "Nicht verbunden"
      callState.value = "idle"
    } catch (error) {
      sipError.value = error?.message || "SIP-Verbindung konnte nicht getrennt werden."
    } finally {
      sipLoading.value = false
    }
  }

  const registerSip = async () => {
    if (!canRegisterSip.value) return

    sipLoading.value = true
    sipError.value = null

    try {
      await stopSip()

      const sip = await ensureSipModule()
      const account = selectedAccount.value
      const uri = sip.UserAgent.makeURI(`sip:${account.extension}@${config.value.sipDomain}`)

      if (!uri) throw new Error("SIP-URI konnte nicht erstellt werden.")

      const handleIncomingInvite = (invitation) => {
        const remoteNumber = invitation.remoteIdentity?.uri?.user || null
        addSipEvent(`INVITE von ${invitation.remoteIdentity?.uri?.user || "unbekannt"} empfangen`)
        incomingCall.value = invitation
        setupSession(invitation, "incoming")
        void createCallRecord({
          direction: "incoming",
          status: "ringing",
          localExtension: account.extension,
          remoteNumber,
          remoteDisplayName: invitation.remoteIdentity?.displayName || remoteNumber,
          session: invitation,
        })
        startCallSignaling()

        toast.add({
          title: "Eingehender Anruf",
          description: `Anruf für ${account.extension}`,
          color: "primary"
        })
      }

      const ua = new sip.UserAgent({
        uri,
        displayName: account.displayName,
        contactName: account.extension,
        authorizationUsername: account.extension,
        authorizationPassword: account.password,
        logBuiltinEnabled: true,
        logLevel: "log",
        logConnector: (level, category, label, content) => {
          if (category === "sip.Transport" && content.includes("Received WebSocket")) {
            addSipEvent("SIP-Nachricht über WebSocket empfangen")
          }

          if (content.includes("INVITE")) {
            addSipEvent(`${category}: ${content.split("\n")[0]}`)
          }

          if (level === "error" || level === "warn") {
            addSipEvent(`${category}: ${content.split("\n")[0]}`)
          }
        },
        delegate: {
          onInvite: handleIncomingInvite,
          onConnect: () => {
            addSipEvent("SIP-WebSocket verbunden")
            sipStatus.value = sipRegistered.value ? `Registriert als ${account.extension}` : "SIP-WebSocket verbunden"
          },
          onDisconnect: () => {
            addSipEvent("SIP-WebSocket getrennt")
            sipRegistered.value = false
            sipStatus.value = "SIP-WebSocket getrennt"
          },
        },
        transportOptions: {
          server: config.value.sipWebSocketUrl,
          keepAliveInterval: 20,
          traceSip: true,
        },
        sessionDescriptionHandlerFactoryOptions: {
          constraints: {
            audio: true,
            video: false,
          },
          peerConnectionConfiguration: {
            iceServers: [],
          },
        },
      })

      const reg = new sip.Registerer(ua, {
        expires: 120,
      })

      reg.stateChange.addListener((state) => {
        registererState.value = state

        if (state === sip.RegistererState.Registered) {
          addSipEvent(`Registrierung ${account.extension}: Registered`)
          sipRegistered.value = true
          sipStatus.value = `Registriert als ${account.extension}`
        }

        if (state === sip.RegistererState.Unregistered || state === sip.RegistererState.Terminated) {
          addSipEvent(`Registrierung ${account.extension}: ${state}`)
          sipRegistered.value = false
          sipStatus.value = "Nicht verbunden"
        }
      })

      userAgent.value = ua
      registerer.value = reg

      await ua.start()
      await reg.register({
        requestDelegate: {
          onAccept: () => {
            addSipEvent(`REGISTER ${account.extension}: 200 OK`)
            sipRegistered.value = true
            sipStatus.value = `Registriert als ${account.extension}`
            registererState.value = sip.RegistererState.Registered
          },
          onReject: (response) => {
            sipRegistered.value = false
            sipStatus.value = "Registrierung abgelehnt"
            sipError.value = `Asterisk hat REGISTER mit HTTP/SIP ${response.message.statusCode} abgelehnt.`
            addSipEvent(`REGISTER ${account.extension}: ${response.message.statusCode}`)
          },
        },
      })
    } catch (error) {
      sipRegistered.value = false
      sipStatus.value = "Nicht verbunden"
      sipError.value = error?.message || "SIP-Registrierung fehlgeschlagen."
    } finally {
      sipLoading.value = false
    }
  }

  const startCall = async () => {
    if (!canStartCall.value) return

    sipLoading.value = true
    sipError.value = null

    try {
      const sip = await ensureSipModule()
      const target = sip.UserAgent.makeURI(`sip:${dialTarget.value.trim()}@${config.value.sipDomain}`)

      if (!target) throw new Error("Zielnummer ist keine gültige SIP-Adresse.")

      const inviter = new sip.Inviter(userAgent.value, target, {
        sessionDescriptionHandlerOptions: {
          constraints: {
            audio: true,
            video: false,
          },
        },
      })

      await createCallRecord({
        direction: "outgoing",
        status: "dialing",
        localExtension: selectedAccount.value?.extension || selectedExtension.value,
        remoteNumber: dialTarget.value.trim(),
        remoteDisplayName: dialTarget.value.trim(),
        session: inviter,
      })
      setupSession(inviter, "outgoing")
      await inviter.invite()
    } catch (error) {
      await finalizeCallRecord("failed")
      activeSession.value = null
      callState.value = "idle"
      sipError.value = error?.message || "Anruf konnte nicht gestartet werden."
    } finally {
      sipLoading.value = false
    }
  }

  const acceptIncomingCall = async () => {
    if (!incomingCall.value) return

    sipLoading.value = true
    sipError.value = null
    stopCallSignaling()

    try {
      await incomingCall.value.accept({
        sessionDescriptionHandlerOptions: {
          constraints: {
            audio: true,
            video: false,
          },
        },
      })
    } catch (error) {
      sipError.value = error?.message || "Anruf konnte nicht angenommen werden."
    } finally {
      sipLoading.value = false
    }
  }

  const rejectIncomingCall = async () => {
    if (!incomingCall.value) return

    try {
      stopCallSignaling()
      await incomingCall.value.reject()
      await finalizeCallRecord("rejected")
    } finally {
      incomingCall.value = null
      activeSession.value = null
      callState.value = "idle"
    }
  }

  const hangupCall = async () => {
    const session = activeSession.value
    if (!session) return

    const { SessionState } = sipModule.value || await ensureSipModule()

    try {
      if (session.state === SessionState.Initial && session.reject) {
        await session.reject()
      } else if (session.state === SessionState.Establishing && session.cancel) {
        await session.cancel()
      } else if (session.state === SessionState.Established && session.bye) {
        await session.bye()
      } else if (session.dispose) {
        session.dispose()
      }
    } finally {
      await finalizeCallRecord()
      stopCallSignaling()
      activeSession.value = null
      incomingCall.value = null
      callState.value = "idle"
      cleanupRemoteAudio()
      sipStatus.value = sipRegistered.value ? "Registriert" : "Nicht verbunden"
    }
  }

  return {
    loading,
    statusLoading,
    websocketTesting,
    callHistoryLoading,
    config,
    status,
    websocketResult,
    callHistory,
    lastUpdated,
    selectedExtension,
    dialTarget,
    activeSession,
    remoteAudio,
    sipLoading,
    sipRegistered,
    sipStatus,
    sipError,
    incomingCall,
    incomingCaller,
    callState,
    registererState,
    sipEvents,
    callSignalStatus,
    selectedAccount,
    canRegisterSip,
    canStartCall,
    canHangup,
    statusColor,
    statusIcon,
    websocketColor,
    loadTelephony,
    loadCallHistory,
    refreshStatus,
    testWebSocket,
    registerSip,
    stopSip,
    startCall,
    acceptIncomingCall,
    rejectIncomingCall,
    hangupCall,
  }
}
