import APIClient
import AVFoundation
import ComponentLibrary
import SwiftUI
import UIKit
import Utilities

private let log = Logger(category: "HookVideoCell")

/// Minimal UIKit video cell that focuses only on video performance
/// All UI overlays are handled in HooksOverlayView in SwiftUI
public final class HookVideoCell: UICollectionViewCell {
    // MARK: - Properties

    public var hook: Hook?
    public var index: Int = 0
    public var isVisible: Bool = false
    public var playbackState: PlaybackState = .notReady
    public var showFollowButton: Bool = false
    public var isFollowing: Bool = false
    public var isLiked: Bool = false
    public var isClipLiked: Bool = false
    public var likeCount: Int = 0
    public var commentCount: Int = 0
    public var clipPlayCount: Int = 0
    public var isReported: Bool = false
    public var isCreatorHidden: Bool = false
    public var isDisliked: Bool = false
    public var isFeedFocused: Bool = false
    public var isMuted: Bool = true
    public var lyrics: LyricsDataV2 = .empty
    public var onTapCallback: ((Int) -> Void)?
    public var onDoubleTapCallback: ((Hook) -> Void)?
    public var onRemixTapped: ((Hook) -> Void)?
    public var onLikeTapped: ((Hook) -> Void)?
    public var onFollowTapped: ((Hook) -> Void)?
    public var onShareTapped: ((Hook) -> Void)?
    public var onCommentsTapped: ((Hook) -> Void)?
    public var onMoreTapped: ((Hook) -> Void)?
    public var onAuthorTapped: ((String) -> Void)?
    public var onAddSongTapped: ((Hook, Bool) -> Void)?
    public var onChangePlaylistTapped: ((Hook) -> Void)?
    public var onSongTapped: ((Clip) -> Void)?
    public var onShowReportedHookTapped: ((String) -> Void)?
    public var onTapToUnmute: (() -> Void)?

    public var heartAnimationTrigger: Int = 0
    // Allows for scale and opacity transitions when scrolling
    private var currentOpacity: CGFloat = 1.0
    private var currentScale: CGFloat = 1.0

    // MARK: - Multi-tap handling

    private var didTapOnce: Bool = false
    private var singleTapTask: Task<Void, Never>?

    // MARK: - Video Layer

    public private(set) lazy var playerLayer: AVPlayerLayer = {
        let layer = AVPlayerLayer()
        layer.videoGravity = .resizeAspectFill
        layer.backgroundColor = UIColor.SemanticV1.backgroundPrimary.cgColor
        layer.isHidden = false

        // Disable implicit animations to prevent the "zoom in" effect
        layer.actions = [
            "bounds": NSNull(),
            "frame": NSNull(),
            "position": NSNull(),
            "contents": NSNull(),
        ]

        return layer
    }()

    // MARK: - Thumbnail Layer

    private lazy var thumbnailLayer: CALayer = {
        let layer = CALayer()
        layer.contentsGravity = .resizeAspectFill
        layer.backgroundColor = UIColor.black.cgColor
        layer.opacity = 0
        layer.cornerRadius = cornerRadius
        layer.masksToBounds = true

        // Disable implicit animations for showing/hiding thumbnail
        layer.actions = [
            "bounds": NSNull(),
            "frame": NSNull(),
            "position": NSNull(),
            "contents": NSNull(),
            "opacity": NSNull(),
        ]

        return layer
    }()

    // MARK: - SwiftUI Overlay Host

    private var overlayHostingController: UIHostingController<HookVideoOverlay>?

    // Work around to acurately handle playback time updates.
    // Handling playback time updates via SlidingWindowState was causes severe performance issues.
    private let overlayPlaybackViewModel = OverlayPlaybackViewModel()
    private var timeObserver: Any?
    private var playerItemStatusObserver: NSKeyValueObservation?

    // KVO observers for player layer visibility
    private var playerLayerReadyObserver: NSKeyValueObservation?

    // Task cancellation for performance optimization
    private var thumbnailLoadingTask: Task<Void, Never>?

    private lazy var gradientLayer: CAGradientLayer = {
        let layer = CAGradientLayer()
        layer.colors = [
            UIColor.black.withAlphaComponent(0.2).cgColor,
            UIColor.black.withAlphaComponent(0.0).cgColor,
            UIColor.black.withAlphaComponent(0.0).cgColor,
            UIColor.black.withAlphaComponent(0.5).cgColor,
        ]
        layer.startPoint = CGPoint(x: 0.5, y: 0.0)
        layer.endPoint = CGPoint(x: 0.5, y: 1.0)
        layer.cornerRadius = cornerRadius
        layer.masksToBounds = true
        return layer
    }()

    // MARK: - Static Properties

    public static let identifier = "HookVideoCell"

    // MARK: - Styling Properties

    private let cornerRadius: CGFloat = 30.0

    // MARK: - Initialization

    override public init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }

    @available(*, unavailable)
    public required init?(coder _: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: - Lifecycle

    override public func layoutSubviews() {
        super.layoutSubviews()

        // Thumbnail layer same size as player layer
        thumbnailLayer.frame = contentView.bounds

        // Video layer fills the entire cell with rounded corners
        playerLayer.frame = contentView.bounds

        gradientLayer.frame = contentView.bounds

        if let overlayView = overlayHostingController?.view {
            overlayView.frame = contentView.bounds
            overlayView.layoutIfNeeded()
        }
    }

    override public func prepareForReuse() {
        super.prepareForReuse()

        // Remove player time observer
        if let observer = timeObserver {
            playerLayer.player?.removeTimeObserver(observer)
            timeObserver = nil
        }

        // Clear KVO observers
        playerItemStatusObserver?.invalidate()
        playerItemStatusObserver = nil
        playerLayerReadyObserver?.invalidate()
        playerLayerReadyObserver = nil

        // Cancel thumbnail loading task
        thumbnailLoadingTask?.cancel()
        thumbnailLoadingTask = nil

        playerLayer.player = nil
        // Don't hide layer in prepareForReuse

        // Remove SwiftUI overlay
        overlayHostingController?.view.removeFromSuperview()
        overlayHostingController = nil

        // Reset state
        hook = nil
        index = 0
        isVisible = true
        playbackState = .ready
        isClipLiked = false
        isFollowing = false
        isReported = false
        isMuted = true
        lyrics = .empty
        overlayPlaybackViewModel.playbackTime = 0.0
        onTapCallback = nil
        onRemixTapped = nil
        onLikeTapped = nil
        onFollowTapped = nil
        onShareTapped = nil
        onCommentsTapped = nil
        onMoreTapped = nil
        onAuthorTapped = nil
        onChangePlaylistTapped = nil
        onSongTapped = nil
        onShowReportedHookTapped = nil
        onTapToUnmute = nil
        // Reset transforms
        currentOpacity = 1.0
        currentScale = 1.0
        alpha = 1.0
        transform = CGAffineTransform.identity

        // Reset tap state
        didTapOnce = false
        singleTapTask?.cancel()
        singleTapTask = nil

        // Reset thumbnail
        thumbnailLayer.contents = nil
        thumbnailLayer.opacity = 0

        heartAnimationTrigger = 0
    }

    // MARK: - Setup

    private func setupUI() {
        contentView.backgroundColor = UIColor.SemanticV1.backgroundPrimary

        // Configure player layer with rounded corners
        playerLayer.cornerRadius = cornerRadius
        playerLayer.masksToBounds = true

        // Add player layer (bottom)
        contentView.layer.addSublayer(playerLayer)
        // Add thumbnail layer (middle - hidden once video loads)
        contentView.layer.addSublayer(thumbnailLayer)
        // Add gradient layer (top)
        contentView.layer.addSublayer(gradientLayer)

        // Add tap gesture for video interaction
        setupTapGestures()
    }

    private func setupTapGestures() {
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleSingleTap))
        tapGesture.numberOfTapsRequired = 1
        contentView.addGestureRecognizer(tapGesture)
        let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
        doubleTapGesture.numberOfTapsRequired = 2
        contentView.addGestureRecognizer(doubleTapGesture)
        tapGesture.require(toFail: doubleTapGesture)
    }

    @objc private func handleSingleTap() {
        onTapCallback?(index)
    }

    @objc private func handleDoubleTap() {
        guard let hook = hook,
              let onDoubleTapCallback = onDoubleTapCallback
        else { return }

        heartAnimationTrigger += 1
        refreshOverlay()

        // Likes the Hook
        onDoubleTapCallback(hook)
    }

    // MARK: - Configuration

    public func configure(
        with hook: Hook,
        index: Int,
        isVisible: Bool,
        playbackState: PlaybackState,
        showFollowButton: Bool,
        isFollowing: Bool = false,
        isLiked: Bool = false,
        isClipLiked: Bool = false,
        likeCount: Int = 0,
        commentCount: Int = 0,
        clipPlayCount: Int = 0,
        lyrics: LyricsDataV2 = .empty,
        isReported: Bool = false,
        isFeedFocused: Bool = false,
        isCreatorHidden: Bool = false,
        onTapCallback: @escaping (Int) -> Void,
        onDoubleTapCallback: @escaping (Hook) -> Void,
        onRemixTapped: @escaping (Hook) -> Void,
        onLikeTapped: @escaping (Hook) -> Void,
        onFollowTapped: @escaping (Hook) -> Void,
        onShareTapped: @escaping (Hook) -> Void,
        onCommentsTapped: @escaping (Hook) -> Void,
        onMoreTapped: @escaping (Hook) -> Void,
        onAuthorTapped: @escaping (String) -> Void,
        onAddSongTapped: @escaping (Hook, Bool) -> Void,
        onChangePlaylistTapped: @escaping (Hook) -> Void,
        onSongTapped: @escaping (Clip) -> Void,
        onShowReportedHookTapped: ((String) -> Void)? = nil,
        isMuted: Bool = true,
        onTapToUnmute: @escaping () -> Void
    ) {
        self.hook = hook
        self.index = index
        self.showFollowButton = showFollowButton
        self.isVisible = isVisible
        self.playbackState = playbackState
        self.isFollowing = isFollowing
        self.isLiked = isLiked
        self.isClipLiked = isClipLiked
        self.likeCount = likeCount
        self.commentCount = commentCount
        self.clipPlayCount = clipPlayCount
        self.lyrics = lyrics
        self.isReported = isReported
        self.isFeedFocused = isFeedFocused
        self.isCreatorHidden = isCreatorHidden
        self.onTapCallback = onTapCallback
        self.onDoubleTapCallback = onDoubleTapCallback
        self.onRemixTapped = onRemixTapped
        self.onLikeTapped = onLikeTapped
        self.onFollowTapped = onFollowTapped
        self.onShareTapped = onShareTapped
        self.onCommentsTapped = onCommentsTapped
        self.onMoreTapped = onMoreTapped
        self.onAuthorTapped = onAuthorTapped
        self.onAddSongTapped = onAddSongTapped
        self.onChangePlaylistTapped = onChangePlaylistTapped
        self.onSongTapped = onSongTapped
        self.onShowReportedHookTapped = onShowReportedHookTapped
        self.isMuted = isMuted
        self.onTapToUnmute = onTapToUnmute
        // Configure the SwiftUI overlay
        let overlay = HookVideoOverlay(
            hook: hook,
            index: index,
            isVisible: isVisible,
            playbackState: playbackState,
            showFollowButton: showFollowButton,
            isFollowing: isFollowing,
            isLiked: isLiked,
            isClipLiked: isClipLiked,
            likeCount: likeCount,
            commentCount: commentCount,
            clipPlayCount: clipPlayCount,
            overlayPlaybackViewModel: overlayPlaybackViewModel,
            lyrics: lyrics,
            isReported: isReported,
            isCreatorHidden: isCreatorHidden,
            heartAnimationTrigger: heartAnimationTrigger,
            onRemixTapped: onRemixTapped,
            onLikeTapped: onLikeTapped,
            onFollowTapped: onFollowTapped,
            onShareTapped: onShareTapped,
            onCommentsTapped: onCommentsTapped,
            onMoreTapped: onMoreTapped,
            onAuthorTapped: onAuthorTapped,
            onAddSongTapped: { [weak self] hook in
                self?.onAddSongTapped?(hook, self?.isClipLiked ?? false)
            },
            onChangePlaylistTapped: onChangePlaylistTapped,
            onSongTapped: onSongTapped,
            onPlayTapped: { [weak self] in
                self?.onTapCallback?(self?.index ?? 0)
            },
            onShowReportedHookTapped: onShowReportedHookTapped,
            isFocused: isFeedFocused,
            isMuted: isMuted,
            onTapToUnmute: onTapToUnmute
        )

        // Update the hosting controller
        if let hostingController = overlayHostingController {
            hostingController.rootView = overlay
            hostingController.view.frame = contentView.bounds
            hostingController.view.setNeedsLayout()
        } else {
            let hostingController = UIHostingController(rootView: overlay)
            hostingController.view.backgroundColor = .clear
            hostingController.view.layer.cornerRadius = cornerRadius
            hostingController.view.layer.masksToBounds = true
            hostingController.view.translatesAutoresizingMaskIntoConstraints = true
            overlayHostingController = hostingController
            contentView.addSubview(hostingController.view)

            // Set the frame explicitly after adding to view hierarchy
            // This prevents the overlay from being missing sometimes.
            // Likely an issue between the UIKit lifecycle and SwiftUI lifecycle
            hostingController.view.frame = contentView.bounds
            hostingController.view.layoutIfNeeded()
        }

        // Load thumbnail if video is not ready
        if playbackState == .loading || playbackState == .notReady {
            loadThumbnail(for: hook)
        }
    }

    public func updateVisibility(_ isVisible: Bool) {
        self.isVisible = isVisible

        if isVisible {
            setupTimeObserverIfNeeded()
        } else {
            removeTimeObserver()
        }

        refreshOverlay()
    }

    private func refreshOverlay() {
        guard let hook = hook else { return }

        let overlay = HookVideoOverlay(
            hook: hook,
            index: index,
            isVisible: isVisible,
            playbackState: playbackState,
            showFollowButton: showFollowButton,
            isFollowing: isFollowing,
            isLiked: isLiked,
            isClipLiked: isClipLiked,
            likeCount: likeCount,
            commentCount: commentCount,
            clipPlayCount: clipPlayCount,
            overlayPlaybackViewModel: overlayPlaybackViewModel,
            lyrics: lyrics,
            isReported: isReported,
            isCreatorHidden: isCreatorHidden,
            heartAnimationTrigger: heartAnimationTrigger,
            onRemixTapped: onRemixTapped ?? { _ in },
            onLikeTapped: onLikeTapped ?? { _ in },
            onFollowTapped: onFollowTapped ?? { _ in },
            onShareTapped: onShareTapped ?? { _ in },
            onCommentsTapped: onCommentsTapped ?? { _ in },
            onMoreTapped: onMoreTapped ?? { _ in },
            onAuthorTapped: onAuthorTapped ?? { _ in },
            onAddSongTapped: { [weak self] hook in
                self?.onAddSongTapped?(hook, self?.isClipLiked ?? false)
            },
            onChangePlaylistTapped: onChangePlaylistTapped ?? { _ in },
            onSongTapped: onSongTapped ?? { _ in },
            onPlayTapped: { [weak self] in
                self?.onTapCallback?(self?.index ?? 0)
            },
            onShowReportedHookTapped: onShowReportedHookTapped,
            isFocused: isFeedFocused,
            isMuted: isMuted,
            onTapToUnmute: onTapToUnmute ?? {}
        )
        overlayHostingController?.rootView = overlay
    }

    public func updateIsOwner(_ isOwner: Bool) {
        self.showFollowButton = !isOwner
        refreshOverlay()
    }

    public func updateFollowStatus(_ isFollowing: Bool) {
        self.isFollowing = isFollowing
        refreshOverlay()
    }

    public func updateLikeStatus(_ isLiked: Bool) {
        self.isLiked = isLiked
        refreshOverlay()
    }

    public func updateLikeCount(_ likeCount: Int) {
        self.likeCount = likeCount
        refreshOverlay()
    }

    public func updateCommentCount(_ commentCount: Int) {
        self.commentCount = commentCount
        refreshOverlay()
    }

    public func updateClipPlayCount(_ clipPlayCount: Int) {
        self.clipPlayCount = clipPlayCount
        refreshOverlay()
    }

    public func updateReportedStatus(_ isReported: Bool) {
        self.isReported = isReported
        refreshOverlay()
    }

    public func updateCreatorHiddenStatus(_ isCreatorHidden: Bool) {
        self.isCreatorHidden = isCreatorHidden
        refreshOverlay()
    }

    public func updateDislikeStatus(_ isDisliked: Bool) {
        self.isDisliked = isDisliked
        refreshOverlay()
    }

    public func updateFocusedStatus(_ isFeedFocused: Bool) {
        self.isFeedFocused = isFeedFocused
        refreshOverlay()
    }

    public func updateClipLiked(_ isLiked: Bool) {
        if isLiked {
            self.isClipLiked = true
            self.refreshOverlay()
        } else {
            self.isClipLiked = false
            refreshOverlay()
        }
    }

    public func updateIsMuted(_ isMuted: Bool) {
        if isMuted {
            self.isMuted = true
            self.refreshOverlay()
        } else {
            self.isMuted = false
            refreshOverlay()
        }
    }

    public func updateLyrics(_ lyrics: LyricsDataV2) {
        self.lyrics = lyrics
        refreshOverlay()
    }

    // MARK: - Player Management

    /*
         Updates the player for the cell if it's different from the current player.
     */
    public func refreshPlayer(_ player: AVPlayer) {
        guard playerLayer.player !== player else {
            return
        }

        if let observer = timeObserver {
            playerLayer.player?.removeTimeObserver(observer)
            timeObserver = nil
        }

        playerLayer.player = player

        contentView.layoutIfNeeded()
        playerLayer.setNeedsDisplay()

        overlayPlaybackViewModel.playbackTime = player.currentTime().seconds
        setupTimeObserverIfNeeded()

        refreshOverlay()

        // Set up observer for when the player layer is ready to display
        setupPlayerLayerReadyObserver()
    }

    public func setPlayer(_ player: AVPlayer?) {
        guard let player else { return }

        // Clean up any existing observers
        if let observer = timeObserver {
            playerLayer.player?.removeTimeObserver(observer)
            timeObserver = nil
        }
        playerItemStatusObserver?.invalidate()
        playerItemStatusObserver = nil
        playerLayerReadyObserver?.invalidate()
        playerLayerReadyObserver = nil

        playerLayer.player = player

        // Set up observer for when the player layer is ready to display
        setupPlayerLayerReadyObserver()

        if player.currentItem?.status == .readyToPlay {
            player.preroll(atRate: 0.0) { [weak self] _ in
                guard let self else { return }
                DispatchQueue.main.async {
                    self.playerLayer.setNeedsDisplay()
                    self.playerLayer.displayIfNeeded()
                }
            }
        }

        playerLayer.layoutIfNeeded()
        contentView.layoutIfNeeded()

        overlayPlaybackViewModel.playbackTime = player.currentTime().seconds
        setupTimeObserverIfNeeded()

        refreshOverlay()
    }

    public func updatePlaybackState(_ newState: PlaybackState) {
        guard self.playbackState != newState else { return }

        self.playbackState = newState
        refreshOverlay()
    }

    // MARK: - Thumbnail Management

    private func loadThumbnail(for hook: Hook) {
        guard let thumbnailUrlString = hook.thumbnailImageUrl,
              let thumbnailUrl = URL(string: thumbnailUrlString)
        else {
            return
        }

        let hookId = hook.id // Capture before background thread thumbnailLoadingTask

        thumbnailLoadingTask?.cancel()
        thumbnailLoadingTask = Task.detached(priority: .utility) { [weak self] in
            do {
                let image = try await RemoteImagePrefetcher.shared.cachedUIImage(for: thumbnailUrl)

                guard !Task.isCancelled,
                      let self = self,
                      let thumbnailImage = image
                else { return }

                await MainActor.run { [weak self] in
                    guard let self = self,
                          self.hook?.id == hookId
                    else { return }
                    self.showThumbnail(thumbnailImage)
                }
            } catch {
                log.telemetry.error(error, message: "Failed to load cached thumbnail for hook: \(hookId)")
            }
        }
    }

    @MainActor
    private func showThumbnail(_ image: UIImage) {
        thumbnailLayer.contents = image.cgImage
        thumbnailLayer.opacity = 1.0
    }

    @MainActor
    private func hideThumbnail() {
        guard playerLayer.isReadyForDisplay else { return }
        thumbnailLayer.opacity = 0
        thumbnailLayer.contents = nil
    }

    // Set up observer for when the player layer is ready to display
    private func setupPlayerLayerReadyObserver() {
        guard playerLayerReadyObserver == nil else { return }

        playerLayerReadyObserver = playerLayer.observe(
            \.isReadyForDisplay,
             options: [.new]
        ) { [weak self] layer, _ in
            guard let self = self,
                  layer.isReadyForDisplay
            else { return }

            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.hideThumbnail()
            }
        }
    }

    // MARK: - Transform Updates

    public func updateTransforms(opacity: CGFloat, scale: CGFloat) {
        currentOpacity = opacity
        currentScale = scale

        self.alpha = opacity
        self.transform = CGAffineTransform(scaleX: scale, y: scale)
    }

    private func setupTimeObserverIfNeeded() {
        guard isVisible,
              timeObserver == nil,
              let player = playerLayer.player else { return }

        let interval = CMTime(seconds: 1.0 / 15.0, preferredTimescale: 600) // 15fps
        timeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
            guard let self, self.isVisible else { return }

            // Update playbackTime to be used by lyrics overlay
            self.overlayPlaybackViewModel.playbackTime = time.seconds
        }
    }

    private func removeTimeObserver() {
        guard let observer = timeObserver,
              let player = playerLayer.player else { return }

        player.removeTimeObserver(observer)
        timeObserver = nil
    }
}
