"use client"

import { useState, useEffect, useRef, useCallback } from "react"
import { io, Socket } from "socket.io-client"

const STUN_SERVERS = {
  iceServers: [
    { urls: "stun:stun.l.google.com:19302" },
    { urls: "stun:stun1.l.google.com:19302" },
  ],
}

const SIGNALING_URL = process.env.NEXT_PUBLIC_SIGNALING_URL || "http://localhost:3007"

export type CallState = "idle" | "calling" | "ringing" | "connecting" | "connected" | "ended" | "missed" | "rejected" | "offline" | "busy" | "waiting" | "participant_joined"

export interface PlatformUser {
  id: string
  username: string
  firstName: string
  lastName: string
  phone: string | null
  avatar: string | null
  online: boolean
}

export interface CallerInfo {
  id: string
  username: string
  firstName: string
  lastName: string
  avatar: string | null
}

export interface Participant {
  id: string
  label: string
}

export function useWebRTCCall(options?: { anonymous?: boolean }) {
  const isAnonymous = options?.anonymous ?? false

  const [callState, setCallState] = useState<CallState>("idle")
  const [isMuted, setIsMuted] = useState(false)
  const [isSpeakerOn, setIsSpeakerOn] = useState(false)
  const [callDuration, setCallDuration] = useState(0)
  const [error, setError] = useState<string | null>(null)
  const [isReady, setIsReady] = useState(false)
  const [remoteStream, setRemoteStream] = useState<MediaStream | null>(null)
  const [callerInfo, setCallerInfo] = useState<CallerInfo | null>(null)
  const [calleeNumber, setCalleeNumber] = useState<string>("")
  const [tokenLoading, setTokenLoading] = useState(true)
  const [participants, setParticipants] = useState<Participant[]>([])

  const socketRef = useRef<Socket | null>(null)
  const pcRef = useRef<RTCPeerConnection | null>(null)
  const localStreamRef = useRef<MediaStream | null>(null)
  const audioRef = useRef<HTMLAudioElement | null>(null)
  const timerRef = useRef<NodeJS.Timeout | null>(null)
  const pendingCallRef = useRef<{ to: string; sdp: any } | null>(null)
  const roomIdRef = useRef<string | null>(null)

  const startTimer = useCallback(() => {
    setCallDuration(0)
    if (timerRef.current) clearInterval(timerRef.current)
    timerRef.current = setInterval(() => {
      setCallDuration((prev) => prev + 1)
    }, 1000)
  }, [])

  useEffect(() => {
    let socket: Socket | null = null
    let cancelled = false

    async function init() {
      try {
        let authToken: string | undefined

        if (!isAnonymous) {
          const res = await fetch("/api/auth/jwt")
          if (res.ok) {
            const data = await res.json()
            authToken = data.token
          }
          if (cancelled) return
        }

        socket = io(SIGNALING_URL, {
          auth: authToken ? { token: authToken } : undefined,
          transports: ["websocket", "polling"],
        })

        socket.on("connect", () => setIsReady(true))
        socket.on("disconnect", () => setIsReady(false))

        if (!isAnonymous) {
          socket.on("call:incoming", ({ from, sdp, callerInfo: info }) => {
            setCallerInfo(info)
            setCallState("ringing")
            pendingCallRef.current = { to: from, sdp }
          })

          socket.on("call:answered", async ({ from, sdp }) => {
            setCallState("connecting")
            if (pcRef.current && sdp) {
              try {
                await pcRef.current.setRemoteDescription(new RTCSessionDescription(sdp))
              } catch {}
            }
          })

          socket.on("call:ice-candidate", async ({ from, candidate }) => {
            if (pcRef.current && candidate) {
              try {
                await pcRef.current.addIceCandidate(new RTCIceCandidate(candidate))
              } catch {}
            }
          })

          socket.on("call:ended", ({ from }) => {
            setCallState("ended")
            cleanupCall()
          })

          socket.on("call:rejected", ({ from }) => {
            setCallState("rejected")
            cleanupCall()
          })

          socket.on("call:busy", ({ from }) => {
            setCallState("busy")
            cleanupCall()
          })

          socket.on("call:user-offline", ({ userId }) => {
            setCallState("offline")
            cleanupCall()
          })
        }

        socket.on("room:waiting", ({ roomId, participants: pList }) => {
          setParticipants(pList || [])
          setCallState("waiting")
        })

        socket.on("room:participant-joined", ({ roomId, participants: pList }) => {
          setParticipants(pList || [])
          setCallState("participant_joined")
        })

        socket.on("room:initiate-offer", async ({ roomId, participants: pList }) => {
          setParticipants(pList || [])
          setCallState("participant_joined")
          try {
            const pc = new RTCPeerConnection(STUN_SERVERS)
            pc.onicecandidate = (e) => {
              if (e.candidate && roomIdRef.current) {
                socket?.emit("room:ice-candidate", {
                  roomId: roomIdRef.current,
                  candidate: e.candidate.toJSON(),
                })
              }
            }
            pc.ontrack = (e) => {
              setRemoteStream(e.streams[0])
              const audio = new Audio()
              audio.srcObject = e.streams[0]
              audio.autoplay = true
              audioRef.current = audio
              audio.play().catch(() => {})
            }
            pc.oniceconnectionstatechange = () => {
              if (pc.iceConnectionState === "disconnected" || pc.iceConnectionState === "failed") {
                setCallState("ended")
                cleanupCall()
              }
            }
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false })
            localStreamRef.current = stream
            stream.getTracks().forEach((track) => pc.addTrack(track, stream))
            pcRef.current = pc

            const offer = await pc.createOffer()
            await pc.setLocalDescription(offer)
            setCallState("connecting")
            socket?.emit("room:offer", { roomId, sdp: pc.localDescription })
          } catch (e) {
            setError("Failed to start call")
            cleanupCall()
          }
        })

        socket.on("room:offer", async ({ sdp }) => {
          try {
            setCallState("connecting")
            const pc = new RTCPeerConnection(STUN_SERVERS)
            pc.onicecandidate = (e) => {
              if (e.candidate && roomIdRef.current) {
                socket?.emit("room:ice-candidate", {
                  roomId: roomIdRef.current,
                  candidate: e.candidate.toJSON(),
                })
              }
            }
            pc.ontrack = (e) => {
              setRemoteStream(e.streams[0])
              const audio = new Audio()
              audio.srcObject = e.streams[0]
              audio.autoplay = true
              audioRef.current = audio
              audio.play().catch(() => {})
            }
            pc.oniceconnectionstatechange = () => {
              if (pc.iceConnectionState === "disconnected" || pc.iceConnectionState === "failed") {
                setCallState("ended")
                cleanupCall()
              }
            }
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false })
            localStreamRef.current = stream
            stream.getTracks().forEach((track) => pc.addTrack(track, stream))
            pcRef.current = pc

            await pc.setRemoteDescription(new RTCSessionDescription(sdp))
            const answer = await pc.createAnswer()
            await pc.setLocalDescription(answer)
            socket?.emit("room:answer", { roomId: roomIdRef.current, sdp: pc.localDescription })
            setCallState("connected")
            startTimer()
          } catch (e) {
            setError("Failed to join call")
            cleanupCall()
          }
        })

        socket.on("room:answer", async ({ sdp }) => {
          if (pcRef.current) {
            try {
              await pcRef.current.setRemoteDescription(new RTCSessionDescription(sdp))
              setCallState("connected")
              startTimer()
            } catch {}
          }
        })

        socket.on("room:ice-candidate", async ({ candidate }) => {
          if (pcRef.current) {
            try {
              await pcRef.current.addIceCandidate(new RTCIceCandidate(candidate))
            } catch {}
          }
        })

        socket.on("room:participant-left", ({ roomId, participants: pList }) => {
          setParticipants(pList || [])
          setCallState("ended")
          cleanupCall()
        })

        socketRef.current = socket
      } finally {
        setTokenLoading(false)
      }
    }

    init()

    return () => {
      cancelled = true
      if (socket) socket.disconnect()
      cleanupCall()
    }
  }, [isAnonymous])

  const cleanupCall = useCallback(() => {
    if (timerRef.current) {
      clearInterval(timerRef.current)
      timerRef.current = null
    }
    if (pcRef.current) {
      pcRef.current.close()
      pcRef.current = null
    }
    if (localStreamRef.current) {
      localStreamRef.current.getTracks().forEach((t) => t.stop())
      localStreamRef.current = null
    }
    setRemoteStream(null)
    setIsMuted(false)
    setIsSpeakerOn(false)
    if (audioRef.current) {
      audioRef.current.pause()
      audioRef.current.srcObject = null
      audioRef.current = null
    }
    setCallDuration(0)
    pendingCallRef.current = null
    roomIdRef.current = null
    setParticipants([])
  }, [])

  const createPeerConnection = useCallback(async () => {
    const pc = new RTCPeerConnection(STUN_SERVERS)

    pc.onicecandidate = (e) => {
      if (e.candidate && pendingCallRef.current) {
        socketRef.current?.emit("call:ice-candidate", {
          to: pendingCallRef.current.to,
          candidate: e.candidate.toJSON(),
        })
      }
    }

    pc.ontrack = (e) => {
      setRemoteStream(e.streams[0])
      const audio = new Audio()
      audio.srcObject = e.streams[0]
      audio.autoplay = true
      audioRef.current = audio
      audio.play().catch(() => {})
    }

    pc.oniceconnectionstatechange = () => {
      if (pc.iceConnectionState === "disconnected" || pc.iceConnectionState === "failed") {
        setCallState("ended")
        cleanupCall()
      }
    }

    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      localStreamRef.current = stream
      stream.getTracks().forEach((track) => pc.addTrack(track, stream))
    } catch {
      setError("Microphone access denied")
    }

    pcRef.current = pc
    return pc
  }, [cleanupCall])

  const lookupUser = useCallback(async (phone: string): Promise<PlatformUser | null> => {
    return new Promise((resolve) => {
      socketRef.current?.emit("user:lookup", { phone }, (result: any) => {
        if (result?.found) {
          resolve(result.user)
        } else {
          resolve(null)
        }
      })
    })
  }, [])

  const makeCall = useCallback(async (phoneNumber: string) => {
    try {
      setError(null)
      setCalleeNumber(phoneNumber)

      const user = await lookupUser(phoneNumber)
      if (!user) {
        setCallState("missed")
        setError("Not on Platform")
        return
      }
      if (!user.online) {
        setCallState("offline")
        setError("User is offline")
        return
      }

      const pc = await createPeerConnection()
      setCallState("calling")

      const offer = await pc.createOffer()
      await pc.setLocalDescription(offer)

      socketRef.current?.emit("call:offer", {
        to: user.id,
        sdp: pc.localDescription,
        callerInfo: null,
      })
      pendingCallRef.current = { to: user.id, sdp: null }

      setCallState("ringing")
    } catch {
      setError("Failed to start call")
      cleanupCall()
    }
  }, [createPeerConnection, lookupUser, cleanupCall])

  const acceptCall = useCallback(async () => {
    if (!pendingCallRef.current) return
    setCallState("connecting")

    try {
      const pc = await createPeerConnection()

      await pc.setRemoteDescription(new RTCSessionDescription(pendingCallRef.current.sdp))
      const answer = await pc.createAnswer()
      await pc.setLocalDescription(answer)

      socketRef.current?.emit("call:answer", {
        to: pendingCallRef.current.to,
        sdp: pc.localDescription,
      })

      setCallState("connected")
      startTimer()
    } catch {
      setError("Failed to accept call")
      cleanupCall()
    }
  }, [createPeerConnection, cleanupCall, startTimer])

  const rejectCall = useCallback(() => {
    if (pendingCallRef.current) {
      socketRef.current?.emit("call:reject", { to: pendingCallRef.current.to })
    }
    setCallState("idle")
    cleanupCall()
  }, [cleanupCall])

  const endCall = useCallback(() => {
    if (roomIdRef.current) {
      socketRef.current?.emit("room:leave", { roomId: roomIdRef.current })
    }
    if (!isAnonymous) {
      if (callState === "ringing" && pendingCallRef.current) {
        socketRef.current?.emit("call:reject", { to: pendingCallRef.current.to })
      }
      if (callState === "connected" || callState === "connecting" || callState === "calling") {
        const to = pendingCallRef.current?.to
        if (to) {
          socketRef.current?.emit("call:end", { to })
        }
      }
    }
    setCallState("ended")
    cleanupCall()
  }, [callState, cleanupCall, isAnonymous])

  const toggleSpeaker = useCallback(() => {
    setIsSpeakerOn((prev) => !prev)
  }, [])

  const toggleMute = useCallback(() => {
    if (localStreamRef.current) {
      localStreamRef.current.getAudioTracks().forEach((track) => {
        track.enabled = isMuted
      })
      setIsMuted(!isMuted)
    }
  }, [isMuted])

  const formatDuration = useCallback((seconds: number) => {
    const m = Math.floor(seconds / 60).toString().padStart(2, "0")
    const s = (seconds % 60).toString().padStart(2, "0")
    return `${m}:${s}`
  }, [])

  const createRoom = useCallback(() => {
    const roomId = crypto.randomUUID?.() || Math.random().toString(36).slice(2, 14)
    const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || (typeof window !== "undefined" ? window.location.origin : "http://localhost:3000")
    const link = `${baseUrl}/join/${roomId}`
    return { roomId, link }
  }, [])

  const joinRoom = useCallback((roomId: string, displayName?: string) => {
    roomIdRef.current = roomId
    setCallState("calling")
    socketRef.current?.emit("room:join", { roomId, displayName: displayName || "" })
  }, [])

  const leaveRoom = useCallback(() => {
    if (roomIdRef.current) {
      socketRef.current?.emit("room:leave", { roomId: roomIdRef.current })
    }
    setCallState("ended")
    cleanupCall()
  }, [cleanupCall])

  return {
    callState,
    isCallActive: callState === "connected" || callState === "connecting" || callState === "ringing" || callState === "calling" || callState === "waiting" || callState === "participant_joined",
    isIncoming: callState === "ringing" && !!callerInfo,
    isMuted,
    isSpeakerOn,
    callDuration,
    error,
    isReady,
    remoteStream,
    callerInfo,
    calleeNumber,
    participants,
    createRoom,
    joinRoom,
    leaveRoom,
    makeCall,
    acceptCall,
    rejectCall,
    endCall,
    toggleMute,
    toggleSpeaker,
    lookupUser,
    formatDuration,
    tokenLoading,
  }
}
