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

// MARK: - Analytics Event Processor

private actor AnalyticsEventProcessor {
    private let analyticsClient: AnalyticsClient
    private let state: HookSessionState

    init(analyticsClient: AnalyticsClient, state: HookSessionState) {
        self.analyticsClient = analyticsClient
        self.state = state
    }

    private func getEventFromAction(
        _ action: Event.ActionName,
        hook: Hook,
        context: HookSessionContext
    ) -> Event {
        let event = Event(
            category: .hooksPlayer,
            actionName: action,
            elementType: .hook,
            elementId: hook.id,
            context: context.toJsonString()
        )
        logAnalyticsEvent("\(action)", hookId: hook.id, context: context)
        return event
    }

    func processPlayNewHook(_ hook: Hook) async {
        // Start new session if needed and record play intent with event type
        if await state.getCurrentSessionForHook(hook) == nil {
            await state.startNewSession(for: hook)
        }
        await state.recordPlayIntent(for: hook, eventType: .initial)
    }

    func processPauseHook(_ hook: Hook, _ currentPositionSeconds: TimeInterval, _ cause: HookPauseCause) async {
        await state.pauseHookSession(hook, currentPositionSeconds: currentPositionSeconds)

        let context = await state.createContextForPausedHook(hook, currentPositionSeconds: currentPositionSeconds, cause: cause)

        let actionName: Event.ActionName
        switch cause {
        case .scroll: actionName = .pauseHook

        case .openOmniPlayer: actionName = .tapSongPillPauseHook

        case .manual: actionName = .pauseHook

        case .navigation(let cause):
            switch cause {
            case .profile: actionName = .tapProfilePauseHook
            case .createHook: actionName = .tapCreateHookPauseHook
            case .createSong: actionName = .tapCreateSongPauseHook
            case .tab: actionName = .tapTabPauseHook
            case .feed: actionName = .tapBackPauseHook
            case .cover: actionName = .tapCoverPauseHook
            case .extend: actionName = .tapExtendPauseHook
            case .onboarding: return // TODO: add analytics event for hooks onboarding flow
            }

        case .appBackground: actionName = .backgroundAppPauseHook

        case .appInactive: actionName = .inactiveAppPauseHook

        case .appTerminate: actionName = .pauseHook

        case .audioInterruption: actionName = .pauseHook

        case .reloadFeed: actionName = .pauseHook

        case .openDeeplink: actionName = .openDeeplinkPauseHook
        }

        let event = getEventFromAction(actionName, hook: hook, context: context)
        analyticsClient.trackV2(event: event, source: "hooks_player")
    }

    func processPlayHook(_ hook: Hook, _: TimeInterval, _ cause: HookPlayCause) async {
        await state.recordPlayIntent(for: hook, eventType: .manual(cause))
    }

    func processScrollDownPauseHook(_ hook: Hook, _ currentPositionSeconds: TimeInterval) async {
        await state.pauseHookSession(hook, currentPositionSeconds: currentPositionSeconds)

        let context = await state.createContextForPausedHook(hook, currentPositionSeconds: currentPositionSeconds)
        let event = getEventFromAction(.scrollDownPauseHook, hook: hook, context: context)
        analyticsClient.trackV2(event: event, source: "hooks_player")
    }

    func processScrollDownPlayNewHook(_ hook: Hook) async {
        if await state.getCurrentSessionForHook(hook) == nil {
            await state.startNewSession(for: hook)
        }
        await state.recordPlayIntent(for: hook, eventType: .scrollDown)
    }

    func processAutoRepeatPauseHook(_ hook: Hook, _ currentPositionSeconds: TimeInterval) async {
        await state.pauseHookSession(hook, currentPositionSeconds: currentPositionSeconds)

        let context = await state.createContextForPausedHook(hook, currentPositionSeconds: currentPositionSeconds)
        let event = getEventFromAction(.autoRepeatPauseHook, hook: hook, context: context)
        analyticsClient.trackV2(event: event, source: "hooks_player")
    }

    func processAutoRepeatPlaySameHook(_ hook: Hook) async {
        await state.recordPlayIntent(for: hook, eventType: .autoRepeat)
    }

    func processScrollUpPauseHook(_ hook: Hook, _ currentPositionSeconds: TimeInterval) async {
        await state.pauseHookSession(hook, currentPositionSeconds: currentPositionSeconds)

        let context = await state.createContextForPausedHook(hook, currentPositionSeconds: currentPositionSeconds)
        let event = getEventFromAction(.scrollUpPauseHook, hook: hook, context: context)
        analyticsClient.trackV2(event: event, source: "hooks_player")
    }

    func processScrollUpPlayNewHook(_ hook: Hook) async {
        if await state.getCurrentSessionForHook(hook) == nil {
            await state.startNewSession(for: hook)
        }
        await state.recordPlayIntent(for: hook, eventType: .scrollUp)
    }

    func processActualPlaybackStart(_ hook: Hook, currentPositionSeconds: TimeInterval = 0) async {
        await state.recordActualPlaybackStart(for: hook, currentPositionSeconds: currentPositionSeconds)

        // Look up the pending play event type that was set when recordPlayIntent was called
        guard let eventType = await state.getPendingPlayEventType() else {
            return
        }

        // Clear it so we don't reuse it
        await state.clearPendingPlayEventType()

        let context = await state.createContextForHook(hook, currentPositionSeconds: currentPositionSeconds)

        let actionName: Event.ActionName
        switch eventType {
        case .initial:
            actionName = .playNewHook
        case .scrollDown:
            actionName = .scrollDownPlayNewHook
        case .scrollUp:
            actionName = .scrollUpPlayNewHook
        case .autoRepeat:
            actionName = .autoRepeatPlayHook
        case .manual(let cause):
            switch cause {
            case .manual: actionName = .playHook

            case .resume(let resumeCause):
                switch resumeCause {
                case .tab: actionName = .tapTabPlayHook
                case .feed: actionName = .tapBackPlayHook
                case .createHook: actionName = .closeCreateHookPlayHook
                case .createSong: actionName = .closeCreateSongPlayHook
                case .cover: actionName = .closeCoverPlayHook
                case .extend: actionName = .closeExtendPlayHook
                case .onboarding: return
                default: actionName = .playHook
                }

            case .appForeground: actionName = .foregroundAppPlayHook

            case .closeOmniPlayer: actionName = .closeOmniPlayerPlayHook

            case .openDeeplink: actionName = .openDeeplinkPlayHook
            }
        }

        let event = getEventFromAction(actionName, hook: hook, context: context)
        analyticsClient.trackV2(event: event, source: "hooks_player")
    }

    private func logAnalyticsEvent(_ actionName: String, hookId: String, context: HookSessionContext) {
        // 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)
        {
            formattedContext = prettyString.components(separatedBy: .newlines)
                .map { "    \($0)" }
                .joined(separator: "\n")
        } else {
            formattedContext = "    \(contextJsonString)"
        }

        let timestamp = Date()
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm:ss.SSS"

        log.info("""

        Action: \(actionName)
        Timestamp: \(formatter.string(from: timestamp))
        HookID: \(hookId)
        Context:
        \(formattedContext)

        """)
    }
}

// Analytics events client for HooksPlayer
@DependencyClient
public struct HooksPlayerAnalyticsClient {
    // Analytics events for Hook playback tracking
    public var trackPlayNewHook: @Sendable (Hook) -> Void = { _ in }
    public var trackPauseHook: @Sendable (Hook, TimeInterval, HookPauseCause) -> Void = { _, _, _ in }
    public var trackPlayHook: @Sendable (Hook, TimeInterval, HookPlayCause) -> Void = { _, _, _ in }
    public var trackAutoRepeatPlaySameHook: @Sendable (Hook) -> Void = { _ in }
    public var trackAutoRepeatPauseHook: @Sendable (Hook, TimeInterval) -> Void = { _, _ in }
    public var trackScrollUpPauseHook: @Sendable (Hook, TimeInterval) -> Void = { _, _ in }
    public var trackScrollDownPauseHook: @Sendable (Hook, TimeInterval) -> Void = { _, _ in }
    public var trackScrollUpPlayNewHook: @Sendable (Hook) -> Void = { _ in }
    public var trackScrollDownPlayNewHook: @Sendable (Hook) -> Void = { _ in }

    // Time-to-first-frame tracking
    public var recordActualPlaybackStart: @Sendable (Hook, TimeInterval) async -> Void = { _, _ in }

    // State management
    public var setupSession: @Sendable (String?) async -> Void = { _ in }
    public var setContext: @Sendable (String?, String?, String?, String?, String?) async -> Void = { _, _, _, _, _ in }
    public var setMuteState: @Sendable (Bool) async -> Void = { _ in }
    public var endPlayback: @Sendable (Hook, TimeInterval) -> Void = { _, _ in }

    // Analytics
    public var getCurrentSessionId: @Sendable () async -> String? = { nil }
}

// MARK: - Implementation

// private let log = Logger(category: "HooksPlayerAnalyticsClient")

extension HooksPlayerAnalyticsClient: DependencyKey {
    public static let liveValue: Self = {
        @Dependency(\.analyticsClient) var analyticsClient
        let state = HookSessionState()
        let processor = AnalyticsEventProcessor(analyticsClient: analyticsClient, state: state)

        return Self(
            trackPlayNewHook: { hook in
                Task.detached(priority: .utility) {
                    await processor.processPlayNewHook(hook)
                }
            },

            trackPauseHook: { hook, currentPositionSeconds, cause in
                Task.detached(priority: .utility) {
                    await processor.processPauseHook(hook, currentPositionSeconds, cause)
                }
            },

            trackPlayHook: { hook, currentPositionSeconds, cause in
                Task.detached(priority: .utility) {
                    await processor.processPlayHook(hook, currentPositionSeconds, cause)
                }
            },

            trackAutoRepeatPlaySameHook: { hook in
                Task.detached(priority: .utility) {
                    await processor.processAutoRepeatPlaySameHook(hook)
                }
            },

            trackAutoRepeatPauseHook: { hook, currentPositionSeconds in
                Task.detached(priority: .utility) {
                    await processor.processAutoRepeatPauseHook(hook, currentPositionSeconds)
                }
            },

            trackScrollUpPauseHook: { hook, currentPositionSeconds in
                Task.detached(priority: .utility) {
                    await processor.processScrollUpPauseHook(hook, currentPositionSeconds)
                }
            },

            trackScrollDownPauseHook: { hook, currentPositionSeconds in
                Task.detached(priority: .utility) {
                    await processor.processScrollDownPauseHook(hook, currentPositionSeconds)
                }
            },

            trackScrollUpPlayNewHook: { hook in
                Task.detached(priority: .utility) {
                    await processor.processScrollUpPlayNewHook(hook)
                }
            },

            trackScrollDownPlayNewHook: { hook in
                Task.detached(priority: .utility) {
                    await processor.processScrollDownPlayNewHook(hook)
                }
            },

            recordActualPlaybackStart: { hook, currentPositionSeconds in
                await processor.processActualPlaybackStart(hook, currentPositionSeconds: currentPositionSeconds)
            },

            setupSession: { userId in
                await state.setCurrentUserId(userId)
            },

            setContext: { contextType, contextId, sourceUrl, navigationIntent, targetCommentId in
                await state.setContext(contextType: contextType, contextId: contextId, sourceUrl: sourceUrl, navigationIntent: navigationIntent, targetCommentId: targetCommentId)
            },

            setMuteState: { isMuted in
                await state.setMuteState(isMuted)
            },

            endPlayback: { hook, currentPositionSeconds in
                Task {
                    await state.pauseHookSession(hook, currentPositionSeconds: currentPositionSeconds)
                }
            },

            getCurrentSessionId: {
                await state.getCurrentSessionId()
            }
        )
    }()
}

public extension HooksPlayerAnalyticsClient {
    static let noop = HooksPlayerAnalyticsClient(
        trackPlayNewHook: { _ in },
        trackPauseHook: { _, _, _ in },
        trackPlayHook: { _, _, _ in },
        trackAutoRepeatPlaySameHook: { _ in },
        trackAutoRepeatPauseHook: { _, _ in },
        trackScrollUpPauseHook: { _, _ in },
        trackScrollDownPauseHook: { _, _ in },
        trackScrollUpPlayNewHook: { _ in },
        trackScrollDownPlayNewHook: { _ in },
        recordActualPlaybackStart: { _, _ async in },
        setupSession: { _ in },
        setContext: { _, _, _, _, _ in },
        setMuteState: { _ in },
        endPlayback: { _, _ in },
        getCurrentSessionId: { nil }
    )
}

public extension DependencyValues {
    var hooksPlayerAnalyticsClient: HooksPlayerAnalyticsClient {
        get { self[HooksPlayerAnalyticsClient.self] }
        set { self[HooksPlayerAnalyticsClient.self] = newValue }
    }
}
