/**
 * usePhone — WebRTC softphone via SIP.js + 3CX PBX.
 *
 * Registers as a SIP UA on the 3CX SBC (WebSocket),
 * can make outbound calls and receive inbound calls.
 *
 * 3CX SIP credentials come from the xAPI: MyUser.AuthID / AuthPassword
 * WSS endpoint is the 3CX SBC: wss://<sbc-fqdn>:443/ws or wss://<fqdn>:5001
 *
 * Usage:
 *   const phone = usePhone()
 *   await phone.register()
 *   await phone.call('+15145551234')
 *   phone.hangup()
 */
import { ref, computed, onUnmounted } from 'vue'
import { UserAgent, Registerer, Inviter, SessionState } from 'sip.js'

// ── 3CX Config (loaded from localStorage, set via Settings page) ──
const STORAGE_KEY = 'ops-phone-config'

const defaults = {
  pbxUrl: 'https://targopbx.3cx.ca',
  wssUrl: 'wss://targopbx.3cx.ca/wss', // SBC WebSocket — adjust once SBC is enabled
  sipDomain: 'targopbx.3cx.ca',
  // Per-user SIP creds (from 3CX MyUser API)
  extension: '',
  authId: '',
  authPassword: '',
  displayName: '',
}

export function getPhoneConfig () {
  try {
    return { ...defaults, ...JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}') }
  } catch {
    return { ...defaults }
  }
}

export function savePhoneConfig (cfg) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(cfg))
}

/**
 * Authenticate to 3CX API and fetch SIP credentials for the user.
 * Returns { extension, authId, authPassword, displayName } or throws.
 */
export async function fetch3cxCredentials (username, password) {
  const cfg = getPhoneConfig()
  // Step 1: Login to get Bearer token
  const loginRes = await fetch(cfg.pbxUrl + '/webclient/api/Login/GetAccessToken', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ Username: username, Password: password }),
  })
  const loginData = await loginRes.json()
  if (loginData.Status !== 'AuthSuccess') {
    throw new Error('3CX login failed: ' + (loginData.Status || 'Unknown'))
  }
  const token = loginData.Token.access_token

  // Step 2: Get SIP credentials from MyUser
  const userRes = await fetch(cfg.pbxUrl + '/xapi/v1/MyUser', {
    headers: { Authorization: 'Bearer ' + token },
  })
  if (!userRes.ok) throw new Error('Failed to fetch 3CX user: ' + userRes.status)
  const user = await userRes.json()

  return {
    extension: user.Number,
    authId: user.AuthID,
    authPassword: user.AuthPassword,
    displayName: (user.FirstName + ' ' + user.LastName).trim(),
    token, // keep for API calls
  }
}

// ── Singleton state (shared across components) ──
const registered = ref(false)
const registering = ref(false)
const callState = ref('idle') // idle | calling | ringing | active | ended
const callDuration = ref(0)
const callDirection = ref('') // out | in
const callRemote = ref('') // remote party number
const callRemoteName = ref('')
const callStartTime = ref(null)
const error = ref('')
const incomingCall = ref(null) // pending incoming session

let ua = null
let registerer = null
let currentSession = null
let durationTimer = null
let audioElement = null

function getAudioElement () {
  if (!audioElement) {
    audioElement = document.createElement('audio')
    audioElement.autoplay = true
    document.body.appendChild(audioElement)
  }
  return audioElement
}

function startDurationTimer () {
  callStartTime.value = Date.now()
  callDuration.value = 0
  durationTimer = setInterval(() => {
    if (callStartTime.value) {
      callDuration.value = Math.floor((Date.now() - callStartTime.value) / 1000)
    }
  }, 1000)
}

function stopDurationTimer () {
  if (durationTimer) {
    clearInterval(durationTimer)
    durationTimer = null
  }
}

function resetCallState () {
  callState.value = 'idle'
  callDuration.value = 0
  callDirection.value = ''
  callRemote.value = ''
  callRemoteName.value = ''
  callStartTime.value = null
  incomingCall.value = null
  currentSession = null
  stopDurationTimer()
}

function attachSessionListeners (session) {
  currentSession = session

  session.stateChange.addListener((state) => {
    switch (state) {
      case SessionState.Establishing:
        callState.value = 'ringing'
        break
      case SessionState.Established:
        callState.value = 'active'
        startDurationTimer()
        // Attach remote audio
        setupRemoteMedia(session)
        break
      case SessionState.Terminated:
        callState.value = 'ended'
        stopDurationTimer()
        cleanupMedia()
        // Auto-reset after 2s
        setTimeout(resetCallState, 2000)
        break
    }
  })
}

function setupRemoteMedia (session) {
  const audio = getAudioElement()
  const pc = session.sessionDescriptionHandler?.peerConnection
  if (pc) {
    pc.getReceivers().forEach((receiver) => {
      if (receiver.track && receiver.track.kind === 'audio') {
        const stream = new MediaStream([receiver.track])
        audio.srcObject = stream
      }
    })
  }
}

function cleanupMedia () {
  if (audioElement) {
    audioElement.srcObject = null
  }
}

export function usePhone () {
  /**
   * Register with 3CX SBC via WebSocket SIP.
   */
  async function register () {
    if (registered.value || registering.value) return
    registering.value = true
    error.value = ''

    const cfg = getPhoneConfig()
    if (!cfg.extension || !cfg.authId || !cfg.authPassword) {
      error.value = 'Phone not configured. Go to Settings → Phone.'
      registering.value = false
      return
    }

    try {
      const uri = UserAgent.makeURI(`sip:${cfg.extension}@${cfg.sipDomain}`)
      if (!uri) throw new Error('Invalid SIP URI')

      ua = new UserAgent({
        uri,
        transportOptions: {
          server: cfg.wssUrl,
        },
        authorizationUsername: cfg.authId,
        authorizationPassword: cfg.authPassword,
        displayName: cfg.displayName || cfg.extension,
        // Log level: warn in prod
        logLevel: 'warn',
        // Handle incoming calls
        delegate: {
          onInvite: (invitation) => {
            callDirection.value = 'in'
            callRemote.value = invitation.remoteIdentity?.uri?.user || 'Unknown'
            callRemoteName.value = invitation.remoteIdentity?.displayName || ''
            callState.value = 'ringing'
            incomingCall.value = invitation
            attachSessionListeners(invitation)
          },
        },
      })

      await ua.start()

      registerer = new Registerer(ua)
      await registerer.register()
      registered.value = true
    } catch (e) {
      error.value = 'Registration failed: ' + e.message
      console.error('[usePhone] register error:', e)
    } finally {
      registering.value = false
    }
  }

  /**
   * Unregister and disconnect.
   */
  async function unregister () {
    try {
      if (currentSession) {
        try { currentSession.bye?.() || currentSession.cancel?.() } catch {}
      }
      if (registerer) await registerer.unregister().catch(() => {})
      if (ua) await ua.stop().catch(() => {})
    } catch {}
    registered.value = false
    registering.value = false
    resetCallState()
    ua = null
    registerer = null
  }

  /**
   * Make outbound call.
   */
  async function call (number) {
    if (!ua || !registered.value) {
      error.value = 'Phone not registered'
      return
    }
    if (callState.value !== 'idle') {
      error.value = 'Already in a call'
      return
    }

    error.value = ''
    const cfg = getPhoneConfig()
    const target = UserAgent.makeURI(`sip:${number}@${cfg.sipDomain}`)
    if (!target) {
      error.value = 'Invalid number'
      return
    }

    callDirection.value = 'out'
    callRemote.value = number
    callState.value = 'calling'

    const inviter = new Inviter(ua, target, {
      sessionDescriptionHandlerOptions: {
        constraints: { audio: true, video: false },
      },
    })

    attachSessionListeners(inviter)

    try {
      await inviter.invite()
    } catch (e) {
      error.value = 'Call failed: ' + e.message
      resetCallState()
    }
  }

  /**
   * Answer incoming call.
   */
  async function answer () {
    if (!incomingCall.value) return
    try {
      await incomingCall.value.accept({
        sessionDescriptionHandlerOptions: {
          constraints: { audio: true, video: false },
        },
      })
    } catch (e) {
      error.value = 'Answer failed: ' + e.message
    }
  }

  /**
   * Reject incoming call.
   */
  function reject () {
    if (incomingCall.value) {
      try { incomingCall.value.reject() } catch {}
      resetCallState()
    }
  }

  /**
   * Hang up active call.
   */
  function hangup () {
    if (!currentSession) return
    try {
      if (callState.value === 'active') {
        currentSession.bye()
      } else {
        // Ringing or calling — cancel
        currentSession.cancel?.() || currentSession.reject?.()
      }
    } catch {
      resetCallState()
    }
  }

  /**
   * Toggle mute.
   */
  const muted = ref(false)
  function toggleMute () {
    if (!currentSession) return
    const pc = currentSession.sessionDescriptionHandler?.peerConnection
    if (pc) {
      pc.getSenders().forEach((sender) => {
        if (sender.track && sender.track.kind === 'audio') {
          sender.track.enabled = !sender.track.enabled
          muted.value = !sender.track.enabled
        }
      })
    }
  }

  /**
   * Toggle hold (re-INVITE with sendonly/recvonly).
   */
  const held = ref(false)
  async function toggleHold () {
    // Simplified hold — mute + hold state
    // Full SIP hold requires re-INVITE, complex with SIP.js
    held.value = !held.value
    toggleMute() // mute audio as basic hold
  }

  /**
   * Send DTMF tone.
   */
  function sendDtmf (tone) {
    if (!currentSession) return
    try {
      currentSession.sessionDescriptionHandler?.sendDtmf(tone)
    } catch {
      // Fallback: INFO method
      try {
        currentSession.info({
          requestOptions: {
            body: {
              contentDisposition: 'render',
              contentType: 'application/dtmf-relay',
              content: `Signal=${tone}\r\nDuration=160`,
            },
          },
        })
      } catch {}
    }
  }

  const formattedDuration = computed(() => {
    const m = Math.floor(callDuration.value / 60)
    const s = callDuration.value % 60
    return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
  })

  onUnmounted(() => {
    // Don't unregister on unmount — keep registration alive across page navigations
    // Only cleanup when explicitly called
  })

  return {
    // State
    registered,
    registering,
    callState,
    callDuration,
    formattedDuration,
    callDirection,
    callRemote,
    callRemoteName,
    incomingCall,
    error,
    muted,
    held,
    // Actions
    register,
    unregister,
    call,
    answer,
    reject,
    hangup,
    toggleMute,
    toggleHold,
    sendDtmf,
  }
}
