//
//  CallVideoView.swift
//  humand
//
//  Native UIView that renders a Stream video participant's video track
//

import UIKit
import StreamVideo
import StreamWebRTC

@objc(CallVideoView)
class CallVideoView: UIView {

  private var videoRenderer: VideoRenderer?
  private var currentSessionId: String?
  private var currentTrackType: String?
  private var isVisibilitySet: Bool = false

  // Event observation tasks for participant/track changes
  private var participantsObservationTask: Task<Void, Never>?

  /// Returns the active Stream call from either CallManager or LivestreamManager.
  private var activeCall: Call? {
    CallManager.shared.streamCall ?? LivestreamManager.shared.streamCall
  }

  @objc var sessionId: String = "" {
    didSet {
      if sessionId != currentSessionId {
        currentSessionId = sessionId
        updateVideoTrack()
      }
    }
  }

  @objc var trackType: String = "videoTrack" {
    didSet {
      if trackType != currentTrackType {
        currentTrackType = trackType
        updateVideoTrack()
      }
    }
  }

  @objc var isMirrored: Bool = false {
    didSet {
      updateMirror()
    }
  }

  @objc var scalingMode: String = "fill" {
    didSet {
      updateScalingMode()
    }
  }

  override init(frame: CGRect) {
    super.init(frame: frame)
    setupView()
  }

  required init?(coder: NSCoder) {
    super.init(coder: coder)
    setupView()
  }

  private func setupView() {
    backgroundColor = .black

    let renderer = VideoRenderer(frame: bounds)
    renderer.videoContentMode = .scaleAspectFill
    renderer.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    renderer.clipsToBounds = true
    addSubview(renderer)

    videoRenderer = renderer
  }

  override func layoutSubviews() {
    super.layoutSubviews()
    videoRenderer?.frame = bounds
  }

  /// Tell the SFU to start/stop sending this participant's track.
  private func setTrackVisibility(_ visible: Bool) {
    guard !sessionId.isEmpty,
          let call = activeCall,
          let participant = findParticipant(in: call) else { return }

    Task {
      await call.changeTrackVisibility(for: participant, isVisible: visible)
      callLog("CallVideoView[\(sessionId)]: changeTrackVisibility=\(visible)")
    }
    isVisibilitySet = visible
  }

  private func updateVideoTrack() {
    callLog("CallVideoView.updateVideoTrack: sessionId=\(sessionId), trackType=\(trackType)")

    stopEventObservation()
    videoRenderer?.removeTrack()

    guard !sessionId.isEmpty else {
      callLog("CallVideoView: sessionId is empty, returning")
      return
    }

    // Subscribe to this participant's track via SFU
    setTrackVisibility(true)

    attemptVideoTrackSetup()
    startEventObservation()
  }

  private func attemptVideoTrackSetup() {
    guard let call = activeCall,
          let renderer = videoRenderer else {
      callLog("CallVideoView[\(sessionId)]: no call or renderer in attemptVideoTrackSetup")
      return
    }

    guard let participant = findParticipant(in: call) else {
      callLog("CallVideoView[\(sessionId)]: participant not found (participants=\(call.state.participants.count), local=\(call.state.localParticipant?.sessionId ?? "nil"))")
      return
    }

    let track = getTrack(for: participant)
    callLog("CallVideoView[\(sessionId)]: attemptVideoTrackSetup track=\(track?.trackId ?? "nil"), hasVideo=\(participant.hasVideo)")
    if let track = track {
      renderer.add(track: track)
    }
  }

  private func startEventObservation() {
    guard let call = activeCall else { return }

    participantsObservationTask = Task { @MainActor [weak self] in
      for await participants in call.state.$participants.values {
        guard let self = self, !Task.isCancelled else { return }
        guard let renderer = self.videoRenderer else { continue }

        let participant = participants.first(where: { $0.sessionId == self.sessionId })
          ?? (call.state.localParticipant?.sessionId == self.sessionId ? call.state.localParticipant : nil)

        callLog("CallVideoView[\(self.sessionId)]: participants updated count=\(participants.count), found=\(participant != nil)")

        if let participant = participant {
          let track = self.getTrack(for: participant)
          callLog("CallVideoView[\(self.sessionId)]: track=\(track?.trackId ?? "nil"), current=\(renderer.track?.trackId ?? "nil"), hasVideo=\(participant.hasVideo)")
          if track?.trackId != renderer.track?.trackId {
            renderer.removeTrack()
            if let track = track {
              callLog("CallVideoView[\(self.sessionId)]: addTrack trackId=\(track.trackId)")
              renderer.add(track: track)
            }
          }
        } else {
          renderer.removeTrack()
        }
      }
    }
  }

  private func stopEventObservation() {
    participantsObservationTask?.cancel()
    participantsObservationTask = nil
  }

  private func findParticipant(in call: Call) -> CallParticipant? {
    if let local = call.state.localParticipant, local.sessionId == sessionId {
      return local
    }
    return call.state.participants.first { $0.sessionId == sessionId }
  }

  private func getTrack(for participant: CallParticipant) -> RTCVideoTrack? {
    switch trackType {
    case "screenShareTrack":
      return participant.screenshareTrack
    default:
      return participant.track
    }
  }

  private func updateMirror() {
    if isMirrored {
      videoRenderer?.transform = CGAffineTransform(scaleX: -1, y: 1)
    } else {
      videoRenderer?.transform = .identity
    }
  }

  private func updateScalingMode() {
    switch scalingMode {
    case "fit":
      videoRenderer?.videoContentMode = .scaleAspectFit
    case "fill":
      videoRenderer?.videoContentMode = .scaleAspectFill
    default:
      videoRenderer?.videoContentMode = .scaleAspectFill
    }
  }

  private func clearTrack() {
    stopEventObservation()
    videoRenderer?.removeTrack()
    if isVisibilitySet {
      setTrackVisibility(false)
    }
    currentSessionId = nil
    currentTrackType = nil
  }

  override func removeFromSuperview() {
    clearTrack()
    callLog("CallVideoView: Cleaned up")
    super.removeFromSuperview()
  }
}
