import APIClient
import Combine
import ComponentLibrary
import ComposableArchitecture
import CoreMedia
import Foundation
import os.log
import StatsigClient
import Utilities

/// State manager for time-synced comments with business logic
/// Handles comment cycling, timing, and state management
public actor TimeSyncCommentState {
    private var timeSyncedComments: [ClipComment] = []
    private var currentClipID: ClipID?

    // Comment cycling (show/hide) state
    private var currentTimeSyncedComment: ClipComment?
    private var isShowingTimeSyncedComment: Bool = false
    private var isInCommentGap: Bool = false

    private var commentTimer: Task<Void, Never>?
    private var eventCallback: ((TimeSyncEvent) -> Void)?

    @Dependency(\.continuousClock) private var clock

    public init() {}

    public func setEventCallback(_ callback: @escaping (TimeSyncEvent) -> Void) {
        self.eventCallback = callback
    }

    public func updateComments(_ comments: [ClipComment], for clipID: ClipID, currentTime: CMTime? = nil) {
        let isNewClip = currentClipID != clipID
        timeSyncedComments = comments
        currentClipID = clipID

        if isNewClip {
            resetForNewClip()
        } else {
            if let currentTime = currentTime {
                checkForEligibleComment(at: currentTime)
            }
        }
    }

    public func setCurrentClipID(_ clipID: ClipID) {
        let isNewClip = currentClipID != clipID
        currentClipID = clipID
        if isNewClip {
            resetForNewClip()
        }
    }

    public func getCurrentClipID() -> ClipID? {
        return currentClipID
    }

    public func handlePlaybackResume(currentTime: CMTime) {
        if isInCommentGap {
            startGapTimer()
        } else if isShowingTimeSyncedComment {
            startDisplayTimer()
        } else if currentTimeSyncedComment == nil {
            checkForEligibleComment(at: currentTime)
        }
    }

    public func handlePlaybackPause() {
        cancelTimer()
    }

    public func handleScrubEnd(at time: CMTime, currentTime: CMTime) {
        let eligibleComments = getEligibleComments(at: time, currentTime: currentTime)
        if let comment = eligibleComments.first {
            startCommentCycle(with: comment)
        } else {
            resetCommentState()
            eventCallback?(.shouldFetchComments)
        }
    }

    public func handleTimeUpdate(currentTime: CMTime) {
        if currentTimeSyncedComment == nil, !isInCommentGap {
            checkForEligibleComment(at: currentTime)
        }
        checkForPrefetch(at: currentTime)
    }

    public func resetForNewClip() {
        cancelTimer()
        timeSyncedComments = []
        trackDuration = nil
        resetCommentState()
        eventCallback?(.shouldHideComment)
    }

    private func checkForEligibleComment(at time: CMTime? = nil) {
        let eligibleComments = getEligibleComments(at: time)
        guard let eligibleComment = eligibleComments.first else {
            if currentTimeSyncedComment != nil || isShowingTimeSyncedComment {
                resetCommentState()
                eventCallback?(.shouldHideComment)
            }
            return
        }
        // Check if we already have this comment showing - avoid unnecessary updates
        if let currentComment = currentTimeSyncedComment, currentComment.id == eligibleComment.id {
            return
        }
        if (currentTimeSyncedComment == nil && !isInCommentGap) || isInCommentGap {
            startCommentCycle(with: eligibleComment)
        }
    }

    private func startCommentCycle(with comment: ClipComment) {
        currentTimeSyncedComment = comment
        isShowingTimeSyncedComment = true
        isInCommentGap = false
        eventCallback?(.shouldShowComment(comment))
        startDisplayTimer()
    }

    private func startDisplayTimer() {
        cancelTimer()
        commentTimer = Task { [weak self, clock] in
            do {
                try await clock.sleep(for: .seconds(Constants.displayDurationSeconds))
                await self?.handleDisplayTimerExpired()
            } catch {
                // Timer was cancelled
            }
        }
    }

    private func startGapTimer() {
        cancelTimer()
        commentTimer = Task { [weak self, clock] in
            do {
                try await clock.sleep(for: .seconds(Constants.gapDurationSeconds))
                await self?.handleGapTimerExpired()
            } catch {
                // Timer was cancelled
            }
        }
    }

    private func handleDisplayTimerExpired() {
        isShowingTimeSyncedComment = false
        isInCommentGap = true
        eventCallback?(.shouldHideComment)
        startGapTimer()
    }

    private func handleGapTimerExpired() {
        let eligibleComments = getEligibleComments()
        let nextComment = eligibleComments.first { comment in
            comment.id != currentTimeSyncedComment?.id
        }

        if let nextComment = nextComment {
            startCommentCycle(with: nextComment)
        } else {
            resetCommentState()
            // Don't fetch more comments immediately on gap expiry
            // Let the natural time update logic handle fetching when needed
            // This prevents making too many API calls
        }
    }

    private func getEligibleComments(at time: CMTime? = nil, currentTime: CMTime? = nil) -> [ClipComment] {
        // Use provided time or get current time from somewhere
        let targetTime: Double
        if let time = time {
            targetTime = time.seconds
        } else if let currentTime = currentTime {
            targetTime = currentTime.seconds
        } else {
            targetTime = 0
        }

        return timeSyncedComments
            .filter { comment in
                !comment.isReply &&
                    comment.trackTimestamp > Constants.minimumTrackTimestamp &&
                    targetTime >= comment.trackTimestamp &&
                    (targetTime - comment.trackTimestamp) <= Constants.commentTimeThreshold
            }
            .sorted { $0.numLikes > $1.numLikes }
    }

    private func resetCommentState() {
        currentTimeSyncedComment = nil
        isShowingTimeSyncedComment = false
        isInCommentGap = false
    }

    private func cancelTimer() {
        commentTimer?.cancel()
        commentTimer = nil
    }

    // MARK: - Prefetching

    private var lastPrefetchTime: Int = 0
    private var trackDuration: Double?

    public func setTrackDuration(_ duration: Double) {
        trackDuration = duration
    }

    private func checkForPrefetch(at currentTime: CMTime) {
        guard let currentClipID = currentClipID else { return }
        let currentSeconds = Int(currentTime.seconds)

        // Only check every interval and look further ahead
        guard currentSeconds >= lastPrefetchTime + Constants.prefetchCheckIntervalSeconds else { return }

        // Don't prefetch near end of track to avoid interfering with auto-advance
        // Use actual track duration if available, otherwise use conservative fallback
        let endThreshold: Double
        if let duration = trackDuration {
            // Avoid buffer time before actual track end
            endThreshold = duration - Constants.autoAdvanceBufferSeconds
        } else {
            // Conservative fallback for unknown duration
            endThreshold = Constants.conservativeFallbackDurationSeconds
        }

        if currentTime.seconds > endThreshold {
            return
        }

        let lookAheadTime = currentSeconds + Constants.prefetchLookAheadSeconds
        // Efficient check: do we have any non-reply comments in the look-ahead window?
        let hasCommentsAhead = timeSyncedComments.contains { comment in
            !comment.isReply &&
                comment.trackTimestamp >= Double(currentSeconds) &&
                comment.trackTimestamp <= Double(lookAheadTime)
        }
        // Only fetch if no comments ahead
        if !hasCommentsAhead {
            lastPrefetchTime = currentSeconds
            eventCallback?(.shouldFetchComments)
        }
    }
}

public enum TimeSyncEvent {
    case shouldShowComment(ClipComment)
    case shouldHideComment
    case shouldFetchComments
}

public extension TimeSyncCommentState {
    enum Constants {
        public static let displayDurationSeconds: Double = 4.0
        public static let gapDurationSeconds: Double = 3.0
        /// How close to track time to show comment
        public static let commentTimeThreshold: Double = 2.0
        /// Don't show comments before this time - allow comments at 0:00s
        public static let minimumTrackTimestamp: Double = 0.0

        public static let fetchAheadWindowSeconds: Double = 60.0
        public static let commentsPerBatch: Int = 50
        public static let fetchMarginSeconds: Int = 2

        /// How often to check for prefetching
        public static let prefetchCheckIntervalSeconds: Int = 15
        public static let prefetchLookAheadSeconds: Int = 30
        /// Buffer time before track end to stop prefetching
        public static let autoAdvanceBufferSeconds: Double = 30
        /// Conservative fallback duration for unknown track lengths
        public static let conservativeFallbackDurationSeconds: Double = 120
    }
}
