import { useState, useCallback, useRef, useEffect } from "react";

const STREAM_SERVER = "https://stream.sociowire.com";
const ICE_SERVERS = [
  { urls: "stun:stun.l.google.com:19302" },
  { urls: "stun:stun1.l.google.com:19302" },
];

/**
 * Hook for WebRTC viewing with join-as-participant capability
 */
export function useWebRTCViewer({ streamID, userID, username, token }) {
  const [isConnected, setIsConnected] = useState(false);
  const [isConnecting, setIsConnecting] = useState(false);
  const [error, setError] = useState(null);

  // Video streams received
  const [mainStream, setMainStream] = useState(null); // Host's main camera
  const [presenterStream, setPresenterStream] = useState(null); // Host's self-view
  const [participantStreams, setParticipantStreams] = useState([]); // Other participants

  // Stream metadata
  const [streamInfo, setStreamInfo] = useState(null);
  const [viewerCount, setViewerCount] = useState(0);

  // Join request state
  const [joinRequestStatus, setJoinRequestStatus] = useState(null); // null, 'pending', 'approved', 'denied'
  const [isParticipant, setIsParticipant] = useState(false);

  const pcRef = useRef(null);
  const wsRef = useRef(null);
  const localStreamRef = useRef(null); // Our camera when we join as participant

  // Connect to event WebSocket
  const connectEventSocket = useCallback(() => {
    if (!streamID) return;

    const protocol = window.location.protocol === "https:" ? "wss" : "ws";
    const wsUrl = `${protocol}://stream.sociowire.com/api/streams/${streamID}/events`;

    try {
      const ws = new WebSocket(wsUrl);

      ws.onopen = () => {
        console.log("[Viewer] Event socket connected");
        ws.send(JSON.stringify({ type: "auth", token, userID, role: "viewer" }));
      };

      ws.onmessage = (event) => {
        try {
          const msg = JSON.parse(event.data);
          handleEventMessage(msg);
        } catch (err) {
          console.error("[Viewer] Failed to parse event:", err);
        }
      };

      ws.onclose = () => {
        console.log("[Viewer] Event socket closed");
      };

      ws.onerror = (err) => {
        console.error("[Viewer] Event socket error:", err);
      };

      wsRef.current = ws;
    } catch (err) {
      console.error("[Viewer] Failed to connect event socket:", err);
    }
  }, [streamID, token, userID]);

  const handleEventMessage = useCallback((msg) => {
    switch (msg.type) {
      case "stream_info":
        setStreamInfo(msg.stream);
        break;

      case "viewer_count":
        setViewerCount(msg.count || 0);
        break;

      case "join_approved":
        setJoinRequestStatus("approved");
        // Now we can send our video
        startParticipating();
        break;

      case "join_denied":
        setJoinRequestStatus("denied");
        break;

      case "participant_joined":
        handleParticipantJoined(msg);
        break;

      case "participant_left":
        handleParticipantLeft(msg.userID);
        break;

      case "host_answer":
        // Host sent us an answer for our participant offer
        handleHostAnswer(msg);
        break;

      case "host_ice":
        handleHostICE(msg);
        break;

      case "stream_ended":
        setError("Stream has ended");
        disconnect();
        break;

      case "kicked":
        setError("You have been removed from the stream");
        setIsParticipant(false);
        stopLocalStream();
        break;

      default:
        console.log("[Viewer] Unknown event:", msg.type);
    }
  }, []);

  const handleParticipantJoined = useCallback((msg) => {
    const { userID: participantID, username: participantName } = msg;

    setParticipantStreams((prev) => [
      ...prev.filter((p) => p.userID !== participantID),
      { userID: participantID, username: participantName, stream: null },
    ]);
  }, []);

  const handleParticipantLeft = useCallback((participantID) => {
    setParticipantStreams((prev) => prev.filter((p) => p.userID !== participantID));
  }, []);

  const handleHostAnswer = useCallback(async (msg) => {
    const { sdp } = msg;
    if (pcRef.current && sdp) {
      try {
        await pcRef.current.setRemoteDescription({ type: "answer", sdp });
        setIsParticipant(true);
        console.log("[Viewer] Now participating with video");
      } catch (err) {
        console.error("[Viewer] Failed to set host answer:", err);
      }
    }
  }, []);

  const handleHostICE = useCallback((msg) => {
    const { candidate } = msg;
    if (pcRef.current && candidate) {
      pcRef.current.addIceCandidate(new RTCIceCandidate(candidate)).catch((err) => {
        console.error("[Viewer] Failed to add host ICE candidate:", err);
      });
    }
  }, []);

  // Connect as viewer (receive only)
  const connect = useCallback(async () => {
    if (isConnecting || isConnected) return;
    setIsConnecting(true);
    setError(null);

    try {
      // Get stream info first
      const infoRes = await fetch(`${STREAM_SERVER}/api/streams/${streamID}`);
      if (!infoRes.ok) {
        throw new Error("Stream not found or has ended");
      }
      const info = await infoRes.json();
      setStreamInfo(info);

      // Create peer connection
      const pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });

      // We need to add transceivers to receive video/audio
      pc.addTransceiver("video", { direction: "recvonly" });
      pc.addTransceiver("video", { direction: "recvonly" }); // For presenter track
      pc.addTransceiver("audio", { direction: "recvonly" });

      pc.ontrack = (event) => {
        const track = event.track;
        const stream = event.streams[0];

        console.log("[Viewer] Received track:", track.kind, stream?.id);

        // Determine which stream this is based on stream ID or order
        // First video stream is main, second is presenter
        if (track.kind === "video") {
          setMainStream((prev) => {
            if (!prev) return stream;
            setPresenterStream(stream);
            return prev;
          });
        }
      };

      pc.onicecandidate = (event) => {
        if (event.candidate && wsRef.current?.readyState === WebSocket.OPEN) {
          wsRef.current.send(
            JSON.stringify({
              type: "ice_candidate",
              candidate: event.candidate,
            })
          );
        }
      };

      pc.onconnectionstatechange = () => {
        console.log("[Viewer] Connection state:", pc.connectionState);
        if (pc.connectionState === "failed" || pc.connectionState === "disconnected") {
          setError("Connection lost");
          setIsConnected(false);
        }
      };

      // Create offer
      const offer = await pc.createOffer();
      await pc.setLocalDescription(offer);

      // Send to server
      const res = await fetch(`${STREAM_SERVER}/api/webrtc/watch/${streamID}`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...(token ? { Authorization: `Bearer ${token}` } : {}),
        },
        body: JSON.stringify({
          sdp: offer.sdp,
          userID,
          username,
        }),
      });

      if (!res.ok) {
        const errData = await res.json().catch(() => ({}));
        throw new Error(errData.error || "Failed to connect to stream");
      }

      const { sdp: answerSDP } = await res.json();
      await pc.setRemoteDescription({ type: "answer", sdp: answerSDP });

      pcRef.current = pc;
      setIsConnected(true);

      // Connect event socket
      connectEventSocket();

      console.log("[Viewer] Connected successfully");
    } catch (err) {
      console.error("[Viewer] Failed to connect:", err);
      setError(err.message || "Failed to connect");
    } finally {
      setIsConnecting(false);
    }
  }, [streamID, userID, username, token, isConnecting, isConnected, connectEventSocket]);

  // Disconnect
  const disconnect = useCallback(() => {
    if (pcRef.current) {
      pcRef.current.close();
      pcRef.current = null;
    }

    if (wsRef.current) {
      wsRef.current.close();
      wsRef.current = null;
    }

    stopLocalStream();

    setIsConnected(false);
    setMainStream(null);
    setPresenterStream(null);
    setParticipantStreams([]);
    setIsParticipant(false);
    setJoinRequestStatus(null);

    console.log("[Viewer] Disconnected");
  }, []);

  // Request to join as participant
  const requestToJoin = useCallback(async () => {
    if (joinRequestStatus === "pending") return;

    try {
      const res = await fetch(
        `${STREAM_SERVER}/api/streams/${streamID}/join-request`,
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            ...(token ? { Authorization: `Bearer ${token}` } : {}),
          },
          body: JSON.stringify({ userID, username }),
        }
      );

      if (!res.ok) {
        throw new Error("Failed to send join request");
      }

      setJoinRequestStatus("pending");
    } catch (err) {
      console.error("[Viewer] Failed to request join:", err);
      setError(err.message);
    }
  }, [streamID, userID, username, token, joinRequestStatus]);

  // Cancel join request
  const cancelJoinRequest = useCallback(async () => {
    try {
      await fetch(
        `${STREAM_SERVER}/api/streams/${streamID}/join-request/${userID}`,
        {
          method: "DELETE",
          headers: {
            ...(token ? { Authorization: `Bearer ${token}` } : {}),
          },
        }
      );
      setJoinRequestStatus(null);
    } catch (err) {
      console.error("[Viewer] Failed to cancel join request:", err);
    }
  }, [streamID, userID, token]);

  // Start participating (send our video after approval)
  const startParticipating = useCallback(async () => {
    try {
      // Get our camera
      const localStream = await navigator.mediaDevices.getUserMedia({
        video: { facingMode: "user", width: 640, height: 480 },
        audio: true,
      });
      localStreamRef.current = localStream;

      // Create new peer connection for sending our stream
      // (or reuse existing one with renegotiation)
      if (pcRef.current) {
        // Add our tracks
        localStream.getTracks().forEach((track) => {
          pcRef.current.addTrack(track, localStream);
        });

        // Renegotiate
        const offer = await pcRef.current.createOffer();
        await pcRef.current.setLocalDescription(offer);

        // Send offer to host via WebSocket
        if (wsRef.current?.readyState === WebSocket.OPEN) {
          wsRef.current.send(
            JSON.stringify({
              type: "participant_offer",
              sdp: offer.sdp,
            })
          );
        }
      }
    } catch (err) {
      console.error("[Viewer] Failed to start participating:", err);
      setError("Failed to access camera");
    }
  }, []);

  // Stop local stream
  const stopLocalStream = useCallback(() => {
    if (localStreamRef.current) {
      localStreamRef.current.getTracks().forEach((track) => track.stop());
      localStreamRef.current = null;
    }
  }, []);

  // Leave as participant (become viewer again)
  const leaveAsParticipant = useCallback(async () => {
    stopLocalStream();
    setIsParticipant(false);

    // Notify server
    try {
      await fetch(
        `${STREAM_SERVER}/api/streams/${streamID}/leave`,
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            ...(token ? { Authorization: `Bearer ${token}` } : {}),
          },
          body: JSON.stringify({ userID }),
        }
      );
    } catch (err) {
      console.error("[Viewer] Failed to notify leave:", err);
    }
  }, [streamID, userID, token, stopLocalStream]);

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      disconnect();
    };
  }, []);

  return {
    isConnected,
    isConnecting,
    error,
    mainStream,
    presenterStream,
    participantStreams,
    streamInfo,
    viewerCount,
    joinRequestStatus,
    isParticipant,
    localStream: localStreamRef.current,
    connect,
    disconnect,
    requestToJoin,
    cancelJoinRequest,
    leaveAsParticipant,
  };
}

export default useWebRTCViewer;
