import AnalyticsClient
import APIClient
import AVFoundation
import ComposableArchitecture
import Foundation
import MediaPlayer
import MusicPlayerClient
import PlayerUtilities
import Utilities

// Analytics events client for OmniPlayer
@DependencyClient
public struct OmniPlayerAnalyticsClient {
    // Analytics events for playback tracking
    public var trackPlayNewSong: @Sendable (Clip, NewSongCause, TimeInterval) -> Void = { _, _, _ in }
    public var trackPlayNewSongPauseSong: @Sendable (Clip, NewSongCause, TimeInterval) -> Void = { _, _, _ in }
    public var trackSongEnd: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackPlaySong: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackPauseSong: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackSeekProgressBarPauseSong: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackSeekProgressBarPlaySong: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackBackwardPauseSong: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackBackwardRepeatSong: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackBackwardPlayNewSong: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackForwardPausePreSong: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackForwardPlayNewSong: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackEndOfQueue: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackCommentTrackTimestampTapped: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var trackPlayNewSongPauseHook: @Sendable (Clip, TimeInterval, NewSongCause) -> Void = { _, _, _ in }
    public var trackPauseSongPlayHook: @Sendable (Clip, TimeInterval, PauseSongCause) -> Void = { _, _, _ in }

    // State management
    public var setupSession: @Sendable (String?) async -> Void = { _ in }
    public var setContext: @Sendable (SessionContext) -> Void = { _ in }
    public var setContextOverride: @Sendable (SessionContext) -> Void = { _ in }
    public var clearContextOverride: @Sendable () -> Void = { }
    public var startPlayback: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var endPlayback: @Sendable (Clip, TimeInterval) -> Void = { _, _ in }
    public var prepareClipChange: @Sendable (Clip, NewSongCause) -> Void = { _, _ in }
    public var getCurrentSessionId: @Sendable () async -> String?
}

// MARK: - Types

public enum NewSongCause: Equatable {
    case vanilla
    case autoAdvance
    case autoRepeat
    case skipBackward
    case skipForward
    case seekToStart
    case hook(id: String, sessionId: String)
}

public enum PauseSongCause: Equatable {
    case manual
    case autoAdvance
    case autoRepeat
    case skipBackward
    case skipForward
    case seekToStart
    case playerDetached // When offloading to SnippetPlayer
    case closeHooksFeedOmniPlayer(hookId: String)
}

// MARK: - Implementation

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

extension OmniPlayerAnalyticsClient: DependencyKey {
    public static let liveValue: Self = {
        // Helper function to log analytics events with pretty formatting
        @Sendable
        func logAnalyticsEvent(_ actionName: String, clipId: String, context: SongSessionContext) {
            // Pretty-print the JSON context
            let formattedContext: String
            let contextJsonString = context.toJsonString()
            if let data = contextJsonString.data(using: .utf8),
               let jsonObject = try? JSONSerialization.jsonObject(with: data),
               let prettyData = try? JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted, .sortedKeys]),
               let prettyString = String(data: prettyData, encoding: .utf8)
            {
                // Indent each line of the JSON for better formatting
                formattedContext = prettyString.components(separatedBy: .newlines)
                    .map { "    \($0)" }
                    .joined(separator: "\n")
            } else {
                formattedContext = "    \(contextJsonString)"
            }

            log.info("""

            Action: \(actionName)
            ClipID: \(clipId)
            Context:
            \(formattedContext)

            """)
        }

        // MARK: - Simple Helper Functions

        @Sendable
        func newSongActionName(_ cause: NewSongCause) -> Event.ActionName {
            switch cause {
            case .vanilla: return .playNewSong
            case .autoAdvance: return .autoRepeatPlayNewSong
            case .autoRepeat: return .autoRepeatPlaySong
            case .skipBackward: return .backwardPlayNewSong
            case .skipForward: return .forwardPlayNewSong
            case .seekToStart: return .backwardRepeatSong
            case .hook: return .tapSongPillPlayNewSong
            }
        }

        @Sendable
        func pauseSongActionName(_ cause: NewSongCause) -> Event.ActionName {
            switch cause {
            case .vanilla: return .playNewSongPauseSong
            case .skipForward: return .forwardPausePreSong
            case .skipBackward: return .backwardPauseSong
            case .autoAdvance, .autoRepeat: return .songEnd
            case .seekToStart: return .backwardPauseSong
            case .hook: return .closeOmniPlayerPauseSong
            }
        }

        @Sendable
        func getEventFromAction(
            _ action: Event.ActionName,
            state: SongSessionState,
            clip: Clip,
            currentPosition: TimeInterval
        ) async -> Event {
            let context: SongSessionContext
            // Pause events must include duration from the session
            switch action {
            case .playNewSongPauseSong,
                 .songEnd,
                 .pauseSong,
                 .seekProgressBarPauseSong,
                 .backwardPauseSong,
                 .backwardRepeatSong,
                 .forwardPausePreSong,
                 .closeOmniPlayerPauseSong:
                context = await state.createContextForClip(clip, currentPositionSeconds: currentPosition, includeDuration: true)
            default:
                context = await state.createContextForClip(clip, currentPositionSeconds: currentPosition)
            }

            let event = Event(
                category: .omniPlayer,
                actionName: action,
                elementType: .clip,
                elementId: clip.id.remoteId,
                context: context.toJsonString()
            )
            logAnalyticsEvent("\(action)", clipId: clip.id.remoteId, context: context)
            return event
        }

        // swiftlint:disable:next function_body_length
        func createAnalyticsClient() -> OmniPlayerAnalyticsClient {
            @Dependency(\.analyticsClient) var analyticsClient

            // Create a shared analytics state object - user ID will be set later
            let state = SongSessionState()

            return Self(
                trackPlayNewSong: { clip, cause, currentPositionSeconds in
                    Task {
                        await state.startNewSession(for: clip)
                        await state.startPlayback(for: clip, currentPositionSeconds: currentPositionSeconds)

                        let actionName = newSongActionName(cause)
                        let event = await getEventFromAction(actionName, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackPlayNewSongPauseSong: { clip, cause, currentPositionSeconds in
                    Task {
                        await state.pauseClipSession(clip, currentPositionSeconds: currentPositionSeconds)

                        let actionName = pauseSongActionName(cause)
                        let event = await getEventFromAction(actionName, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackSongEnd: { clip, currentPositionSeconds in
                    Task {
                        await state.endClipSession(clip, currentPositionSeconds: currentPositionSeconds)

                        let event = await getEventFromAction(.songEnd, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackPlaySong: { clip, currentPositionSeconds in
                    Task {
                        await state.startPlayback(for: clip, currentPositionSeconds: currentPositionSeconds)

                        let event = await getEventFromAction(.playSong, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackPauseSong: { clip, currentPositionSeconds in
                    Task {
                        await state.pauseClipSession(clip, currentPositionSeconds: currentPositionSeconds)

                        let event = await getEventFromAction(.pauseSong, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackSeekProgressBarPauseSong: { clip, currentPositionSeconds in
                    Task {
                        await state.startScrubbing(fromPositionSeconds: currentPositionSeconds)
                        await state.pauseClipSession(clip, currentPositionSeconds: currentPositionSeconds)

                        let event = await getEventFromAction(.seekProgressBarPauseSong, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackSeekProgressBarPlaySong: { clip, currentPositionSeconds in
                    Task {
                        _ = await state.endScrubbing()
                        await state.startPlayback(for: clip, currentPositionSeconds: currentPositionSeconds)

                        let event = await getEventFromAction(.seekProgressBarPlaySong, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackBackwardPauseSong: { clip, currentPositionSeconds in
                    Task {
                        await state.pauseClipSession(clip, currentPositionSeconds: currentPositionSeconds)

                        let event = await getEventFromAction(.backwardPauseSong, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackBackwardRepeatSong: { clip, currentPositionSeconds in
                    Task {
                        await state.pauseClipSession(clip, currentPositionSeconds: currentPositionSeconds)

                        let event = await getEventFromAction(.backwardRepeatSong, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackBackwardPlayNewSong: { clip, currentPositionSeconds in
                    Task {
                        await state.startNewSession(for: clip)
                        await state.startPlayback(for: clip, currentPositionSeconds: currentPositionSeconds)

                        let event = await getEventFromAction(.backwardPlayNewSong, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackForwardPausePreSong: { clip, currentPositionSeconds in
                    Task {
                        await state.pauseClipSession(clip, currentPositionSeconds: currentPositionSeconds)

                        let event = await getEventFromAction(.forwardPausePreSong, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackForwardPlayNewSong: { clip, currentPositionSeconds in
                    Task {
                        await state.startNewSession(for: clip)
                        await state.startPlayback(for: clip, currentPositionSeconds: currentPositionSeconds)

                        let event = await getEventFromAction(.forwardPlayNewSong, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackEndOfQueue: { clip, currentPositionSeconds in
                    Task {
                        let event = await getEventFromAction(.endOfQueue, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackCommentTrackTimestampTapped: { clip, currentPositionSeconds in
                    Task {
                        let event = await getEventFromAction(.commentTrackTimestampTapped, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackPlayNewSongPauseHook: { clip, currentPositionSeconds, cause in
                    Task {
                        await state.startNewSession(for: clip, cause: cause)
                        await state.startPlayback(for: clip, currentPositionSeconds: currentPositionSeconds, cause: cause)

                        let event = await getEventFromAction(.tapSongPillPlayNewSong, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                trackPauseSongPlayHook: { clip, currentPositionSeconds, cause in
                    Task {
                        await state.pauseClipSession(clip, currentPositionSeconds: currentPositionSeconds, cause: cause)

                        let event = await getEventFromAction(.closeOmniPlayerPauseSong, state: state, clip: clip, currentPosition: currentPositionSeconds)
                        analyticsClient.trackV2(event: event, source: "omni_player_v3")
                    }
                },

                // State management methods
                setupSession: { userId in
                    await state.setCurrentUserId(userId)
                },
                setContext: { context in
                    Task {
                        await state.setContext(context)
                    }
                },
                setContextOverride: { context in
                    Task {
                        await state.setContextOverride(context)
                    }
                },
                clearContextOverride: {
                    Task {
                        await state.clearContextOverride()
                    }
                },
                startPlayback: { clip, currentPositionSeconds in
                    Task {
                        await state.startPlayback(for: clip, currentPositionSeconds: currentPositionSeconds)
                    }
                },

                endPlayback: { clip, currentPositionSeconds in
                    Task {
                        await state.pauseClipSession(clip, currentPositionSeconds: currentPositionSeconds)
                    }
                },

                prepareClipChange: { clip, cause in
                    Task {
                        await state.prepareClipChange(to: clip, cause: cause)
                    }
                },
                getCurrentSessionId: {
                    await state.getCurrentSessionId()
                }
            )
        }

        return createAnalyticsClient()
    }()
}

public extension OmniPlayerAnalyticsClient {
    static let noop = OmniPlayerAnalyticsClient(
        trackPlayNewSong: { _, _, _ in },
        trackPlayNewSongPauseSong: { _, _, _ in },
        trackSongEnd: { _, _ in },
        trackPlaySong: { _, _ in },
        trackPauseSong: { _, _ in },
        trackSeekProgressBarPauseSong: { _, _ in },
        trackSeekProgressBarPlaySong: { _, _ in },
        trackBackwardPauseSong: { _, _ in },
        trackBackwardRepeatSong: { _, _ in },
        trackBackwardPlayNewSong: { _, _ in },
        trackForwardPausePreSong: { _, _ in },
        trackForwardPlayNewSong: { _, _ in },
        trackEndOfQueue: { _, _ in },
        trackCommentTrackTimestampTapped: { _, _ in },
        trackPlayNewSongPauseHook: { _, _, _ in },
        trackPauseSongPlayHook: { _, _, _ in },
        setupSession: { _ in },
        setContext: { _ in },
        setContextOverride: { _ in },
        clearContextOverride: { },
        startPlayback: { _, _ in },
        endPlayback: { _, _ in },
        prepareClipChange: { _, _ in },
        getCurrentSessionId: { nil }
    )
}
