import APIClient
import AVFoundation
import Foundation
import MediaPlayer
import Utilities

/// Actor to manage SongSession
public actor SongSessionState {
    // Session tracking
    private var songSessions: [String: SongSession] = [:]
    private var activeSessionId: String?
    private var previousSessionId: String?
    private var clipToSessionMap: [String: String] = [:]
    private var isScrubbing: Bool = false
    private var scrubStartTime: TimeInterval = 0
    private var scrubEndTime: TimeInterval = 0

    private var currentUserId: String?
    private var currentSessionContext: SessionContext?
    private var currentSessionContextOverride: SessionContext?

    public struct SongSession {
        let sessionId: String
        let clipId: String
        var isOwned: Bool
        var playStartPosition: TimeInterval = 0 // Position in song when play started (seconds)
        var playEndPosition: TimeInterval = 0 // Position in song when play ended (seconds)
        var totalPlayDuration: TimeInterval = 0 // Total time spent playing (accumulated)
        var isCurrentlyPlaying: Bool = false
        var hookId: String? = nil // When navigating to a Hook directly from the song (eg. closing OmniPlayer when open from a Hook)
        var previousHookId: String? = nil // Source Hook ID
        var previousHookSessionId: String? = nil // Source Hook Session ID
        var playDuration: TimeInterval {
            return playEndPosition - playStartPosition
        }
    }

    public init() {
        self.currentUserId = nil
    }

    public func setCurrentUserId(_ userId: String?) {
        self.currentUserId = userId
    }

    public func setContext(_ sessionContext: SessionContext) {
        self.currentSessionContext = sessionContext
    }

    public func setContextOverride(_ context: SessionContext) {
        self.currentSessionContextOverride = context
    }

    public func clearContextOverride() {
        self.currentSessionContextOverride = nil
    }

    // MARK: - Session Management

    public func startNewSession(for clip: Clip, cause: NewSongCause = .vanilla) {
        // Store previous session ID and mark it as no longer active
        if let activeId = activeSessionId,
           let currentSession = songSessions[activeId]
        {
            // Only store the previous session ID if it's for a different clip
            if currentSession.clipId != clip.id.remoteId {
                previousSessionId = activeId
            }
            // Mark the session as no longer active
            var updatedSession = currentSession
            updatedSession.isCurrentlyPlaying = false
            songSessions[activeId] = updatedSession
        }

        let newSessionId = UUID().uuidString
        let isOwned = currentUserId != nil && clip.userId == currentUserId
        let previousHookId: String?
        let previousHookSessionId: String?
        if case .hook(let hookId, let hookSessionId) = cause {
            previousHookId = hookId
            previousHookSessionId = hookSessionId
        } else {
            previousHookId = nil
            previousHookSessionId = nil
        }

        songSessions[newSessionId] = SongSession(
            sessionId: newSessionId,
            clipId: clip.id.remoteId,
            isOwned: isOwned,
            playStartPosition: 0,
            playEndPosition: 0,
            totalPlayDuration: 0,
            isCurrentlyPlaying: false,
            previousHookId: previousHookId,
            previousHookSessionId: previousHookSessionId
        )

        clipToSessionMap[clip.id.remoteId] = newSessionId
        activeSessionId = newSessionId
    }

    public func prepareClipChange(to newClip: Clip, cause: NewSongCause = .vanilla) {
        startNewSession(for: newClip, cause: cause)
        isScrubbing = false
    }

    // Handle seek operations
    public func startScrubbing(fromPositionSeconds: TimeInterval) {
        isScrubbing = true
        scrubStartTime = fromPositionSeconds
    }

    public func endScrubbing() -> (TimeInterval, TimeInterval)? {
        guard isScrubbing else { return nil }

        let result = (scrubStartTime, scrubEndTime)
        isScrubbing = false
        return result
    }

    public func startPlayback(for clip: Clip, currentPositionSeconds: TimeInterval = 0, cause: NewSongCause = .vanilla) {
        if getCurrentSessionForClip(clip) == nil {
            startNewSession(for: clip, cause: cause)
        }

        if let sessionId = clipToSessionMap[clip.id.remoteId],
           var session = songSessions[sessionId]
        {
            session.isCurrentlyPlaying = true
            // Normalize very small start times to 0 for cleaner analytics (handles fast swiping)
            session.playStartPosition = currentPositionSeconds < 0.001 ? 0.0 : currentPositionSeconds
            songSessions[sessionId] = session

            if case .hook(let hookId, let hookSessionId) = cause {
                session.previousHookId = hookId
                session.previousHookSessionId = hookSessionId
            }
        }
    }

    public func pauseClipSession(_ clip: Clip, currentPositionSeconds: TimeInterval, cause: PauseSongCause = .manual) {
        guard let sessionId = clipToSessionMap[clip.id.remoteId],
              var session = songSessions[sessionId]
        else {
            return
        }

        // Only accumulate play duration if the session was actually playing
        if session.isCurrentlyPlaying {
            session.playEndPosition = currentPositionSeconds
            let segmentDuration = session.playEndPosition - session.playStartPosition
            if segmentDuration > 0 {
                session.totalPlayDuration += segmentDuration
            }
        } else {
            // If already paused (e.g., during seek), just update the position
            session.playEndPosition = currentPositionSeconds
        }

        if case .closeHooksFeedOmniPlayer(let hookId) = cause {
            session.hookId = hookId
        }

        session.isCurrentlyPlaying = false
        songSessions[sessionId] = session
    }

    public func endClipSession(_ clip: Clip, currentPositionSeconds: TimeInterval? = nil) {
        guard let sessionId = clipToSessionMap[clip.id.remoteId],
              var session = songSessions[sessionId]
        else {
            return
        }

        session.isCurrentlyPlaying = false

        if let currentPos = currentPositionSeconds {
            session.playEndPosition = currentPos

            let segmentDuration = session.playEndPosition - session.playStartPosition
            if segmentDuration > 0 {
                session.totalPlayDuration += segmentDuration
            }
        }

        songSessions[sessionId] = session
    }

    public func getCurrentSessionForClip(_ clip: Clip) -> SongSession? {
        guard let sessionId = clipToSessionMap[clip.id.remoteId],
              let session = songSessions[sessionId]
        else {
            return nil
        }
        return session
    }

    // MARK: - Context Generation

    private func getCurrentVolume() -> Float? {
        // Get system volume from MediaPlayer
        return AVAudioSession.sharedInstance().outputVolume
    }

    public func createContext() -> SongSessionContext {
        // Try to get ownership from the current active session
        let isUserSongOwner = getCurrentActiveSession()?.isOwned ?? false

        let analytics = currentSessionContextOverride?.analyticsContext ?? currentSessionContext?.analyticsContext

        return SongSessionContext(
            songSessionId: getCurrentSessionId() ?? UUID().uuidString,
            previousSongSessionId: previousSessionId,
            startTime: 0,
            endTime: nil,
            playDuration: nil,
            isUserSongOwner: isUserSongOwner,
            volume: getCurrentVolume(),
            contextType: analytics?.contextType,
            contextId: analytics?.contextId,
            sourceUrl: analytics?.sourceUrl,
            navigationIntent: analytics?.navigationIntent,
            targetCommentId: analytics?.targetCommentId
        )
    }

    public func createContextForClip(
        _ clip: Clip,
        currentPositionSeconds: TimeInterval = 0,
        includeDuration: Bool = false
    ) -> SongSessionContext {
        guard let sessionId = clipToSessionMap[clip.id.remoteId],
              let session = songSessions[sessionId]
        else {
            return createContext()
        }

        // Normalize very small start times to 0 for cleaner analytics (handles fast swiping)
        let normalizedStartTime = currentPositionSeconds < 0.001 ? 0.0 : currentPositionSeconds

        let analytics = currentSessionContextOverride?.analyticsContext ?? currentSessionContext?.analyticsContext

        return SongSessionContext(
            songSessionId: session.sessionId,
            previousSongSessionId: previousSessionId,
            startTime: normalizedStartTime,
            endTime: nil,
            playDuration: includeDuration ? (session.playDuration > 0 ? session.playDuration : nil) : nil,
            totalPlayDuration: includeDuration ? (session.totalPlayDuration > 0 ? session.totalPlayDuration : nil) : nil,
            isUserSongOwner: session.isOwned,
            volume: getCurrentVolume(),
            previousHookId: session.previousHookId,
            previousHookSessionId: session.previousHookSessionId,
            hookId: session.hookId,
            contextType: analytics?.contextType,
            contextId: analytics?.contextId,
            sourceUrl: analytics?.sourceUrl,
            navigationIntent: analytics?.navigationIntent,
            targetCommentId: analytics?.targetCommentId
        )
    }

    public func getCurrentSessionId() -> String? {
        return activeSessionId
    }

    public func getCurrentActiveSession() -> SongSession? {
        return songSessions.values.first(where: { $0.isCurrentlyPlaying })
    }
}
