import AnalyticsClient
import APIClient
import ComposableArchitecture
import Foundation
import Utilities

extension HooksEditor {
    struct Analytics: AnalyticsReducer {
        var source: String = "hooks_editor"

        // swiftlint:disable:next cyclomatic_complexity
        func analytics(before: State, after _: State, action: Action) -> Effect<Action> {
            switch action {
            case .closeTapped:
                track(
                    Event(
                        category: .hooksCreate,
                        actionName: .closeTapped,
                        actionType: .tap,
                        elementType: .button,
                        elementId: before.selectedMovie?.url.absoluteString,
                        context: createContext(
                            videoId: before.selectedMovie?.url.absoluteString,
                            clipId: before.clip.id.remoteId,
                            clipVolumeLevel: before.snippetVolumeLevel,
                            videoVolumeLevel: before.videoVolumeLevel,
                            clipStartTime: before.snippetStartTime,
                            clipEndTime: before.snippetEndTime,
                            videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                            videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                        )
                    )
                )

            case .presentVolumeSelector:
                track(
                    Event(
                        category: .hooksCreate,
                        actionName: .volumeSelectorTapped,
                        actionType: .tap,
                        elementType: .button,
                        elementId: before.selectedMovie?.url.absoluteString,
                        context: createContext(
                            videoId: before.selectedMovie?.url.absoluteString,
                            clipId: before.clip.id.remoteId,
                            clipVolumeLevel: before.snippetVolumeLevel,
                            videoVolumeLevel: before.videoVolumeLevel,
                            clipStartTime: before.snippetStartTime,
                            clipEndTime: before.snippetEndTime,
                            videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                            videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                        )
                    )
                )

            case .presentMediaPicker:
                track(
                    Event(
                        category: .hooksCreate,
                        actionName: .mediaPickerTapped,
                        actionType: .tap,
                        elementType: .button,
                        elementId: before.selectedMovie?.url.absoluteString,
                        context: createContext(
                            videoId: before.selectedMovie?.url.absoluteString,
                            clipId: before.clip.id.remoteId,
                            clipVolumeLevel: before.snippetVolumeLevel,
                            videoVolumeLevel: before.videoVolumeLevel,
                            clipStartTime: before.snippetStartTime,
                            clipEndTime: before.snippetEndTime,
                            videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                            videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                        )
                    )
                )
                
            case .setSelectedMovie(let movie):
                track(
                    Event(
                        category: .hooksCreate,
                        actionName: .videoSelected,
                        actionType: .tap,
                        elementType: .video,
                        elementId: movie?.url.absoluteString,
                        context: createContext(
                            videoId: movie?.url.absoluteString,
                            clipId: before.clip.id.remoteId,
                            clipVolumeLevel: before.snippetVolumeLevel,
                            videoVolumeLevel: before.videoVolumeLevel,
                            clipStartTime: before.snippetStartTime,
                            clipEndTime: before.snippetEndTime,
                            videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                            videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                        )
                    )
                )

            case .internal(let event):
                switch event {
                case .goToSnippetSelection:
                    track(
                        Event(
                            category: .hooksCreate,
                            actionName: .snippetSelectionTapped,
                            actionType: .tap,
                            elementType: .button,
                            elementId: before.clip.id.remoteId,
                            context: createContext(
                                videoId: before.selectedMovie?.url.absoluteString,
                                clipId: before.clip.id.remoteId,
                                clipVolumeLevel: before.snippetVolumeLevel,
                                videoVolumeLevel: before.videoVolumeLevel,
                                clipStartTime: before.snippetStartTime,
                                clipEndTime: before.snippetEndTime,
                                videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                                videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                            )
                        )
                    )

                case .nextTapped:
                    track(
                        Event(
                            category: .hooksCreate,
                            actionName: .nextTapped,
                            actionType: .tap,
                            elementType: .button,
                            elementId: before.selectedMovie?.url.absoluteString,
                            context: createContext(
                                videoId: before.selectedMovie?.url.absoluteString,
                                clipId: before.clip.id.remoteId,
                                clipVolumeLevel: before.snippetVolumeLevel,
                                videoVolumeLevel: before.videoVolumeLevel,
                                clipStartTime: before.snippetStartTime,
                                clipEndTime: before.snippetEndTime,
                                videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                                videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                            )
                        )
                    )
                    
                default:
                    break
                }

            case .editing(let editingAction):
                switch editingAction {
                case .startTrimming:
                    track(
                        Event(
                            category: .hooksCreate,
                            actionName: .videoTrimmerTapped,
                            actionType: .tap,
                            elementType: .button,
                            elementId: before.selectedMovie?.url.absoluteString,
                            context: createContext(
                                videoId: before.selectedMovie?.url.absoluteString,
                                clipId: before.clip.id.remoteId,
                                clipVolumeLevel: before.snippetVolumeLevel,
                                videoVolumeLevel: before.videoVolumeLevel,
                                clipStartTime: before.snippetStartTime,
                                clipEndTime: before.snippetEndTime,
                                videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                                videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                            )
                        )
                    )

                case .inline(let inlineAction):
                    switch inlineAction {
                    case .trimmer(let trimmerAction):
                        switch trimmerAction {
                        case .handleDragEnd:
                            track(
                                Event(
                                    category: .hooksCreate,
                                    actionName: .videoTrimmerHandleDragEnded,
                                    actionType: .drag,
                                    elementType: .slider,
                                    elementId: before.selectedMovie?.url.absoluteString,
                                    context: createContext(
                                        videoId: before.selectedMovie?.url.absoluteString,
                                        clipId: before.clip.id.remoteId,
                                        clipVolumeLevel: before.snippetVolumeLevel,
                                        videoVolumeLevel: before.videoVolumeLevel,
                                        clipStartTime: before.snippetStartTime,
                                        clipEndTime: before.snippetEndTime,
                                        videoStartTime: (before.effectiveTrimStart ?? before.originalStartTime),
                                        videoEndTime: (before.effectiveTrimEnd ?? before.originalEndTime)
                                    )
                                )
                            )

                        case .save:
                            track(
                                Event(
                                    category: .hooksCreate,
                                    actionName: .videoTrimmerSaveTapped,
                                    actionType: .tap,
                                    elementType: .button,
                                    elementId: before.selectedMovie?.url.absoluteString,
                                    context: createContext(
                                        videoId: before.selectedMovie?.url.absoluteString,
                                        clipId: before.clip.id.remoteId,
                                        clipVolumeLevel: before.snippetVolumeLevel,
                                        videoVolumeLevel: before.videoVolumeLevel,
                                        clipStartTime: before.snippetStartTime,
                                        clipEndTime: before.snippetEndTime,
                                        videoStartTime: (before.effectiveTrimStart ?? before.originalStartTime),
                                        videoEndTime: (before.effectiveTrimEnd ?? before.originalEndTime)
                                    )
                                )
                            )

                        case .dismissTapped, .dismiss:
                            track(
                                Event(
                                    category: .hooksCreate,
                                    actionName: .videoTrimmerCancelTapped,
                                    actionType: .tap,
                                    elementType: .button,
                                    elementId: before.selectedMovie?.url.absoluteString,
                                    context: createContext(
                                        videoId: before.selectedMovie?.url.absoluteString,
                                        clipId: before.clip.id.remoteId,
                                        clipVolumeLevel: before.snippetVolumeLevel,
                                        videoVolumeLevel: before.videoVolumeLevel,
                                        clipStartTime: before.snippetStartTime,
                                        clipEndTime: before.snippetEndTime,
                                        videoStartTime: (before.effectiveTrimStart ?? before.originalStartTime),
                                        videoEndTime: (before.effectiveTrimEnd ?? before.originalEndTime)
                                    )
                                )
                            )

                        default:
                            break
                        }

                    case .snippet(let snippetAction):
                        switch snippetAction {
                        case .delegate(let delegateAction):
                            switch delegateAction {
                            case .waveformDragStarted:
                                track(
                                    Event(
                                        category: .hooksCreate,
                                        actionName: .snippetSelectionWaveformDragStarted,
                                        actionType: .drag,
                                        elementType: .waveform,
                                        elementId: before.clip.id.remoteId,
                                        context: createContext(
                                            videoId: before.selectedMovie?.url.absoluteString,
                                            clipId: before.clip.id.remoteId,
                                            clipVolumeLevel: before.snippetVolumeLevel,
                                            videoVolumeLevel: before.videoVolumeLevel,
                                            clipStartTime: before.snippetStartTime,
                                            clipEndTime: before.snippetEndTime,
                                            videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                                            videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                                        )
                                    )
                                )

                            case .waveformDragEnded(let timeRange):
                                track(
                                    Event(
                                        category: .hooksCreate,
                                        actionName: .snippetSelectionWaveformDragEnded,
                                        actionType: .drag,
                                        elementType: .waveform,
                                        elementId: before.clip.id.remoteId,
                                        context: createContext(
                                            videoId: before.selectedMovie?.url.absoluteString,
                                            clipId: before.clip.id.remoteId,
                                            clipVolumeLevel: before.snippetVolumeLevel,
                                            videoVolumeLevel: before.videoVolumeLevel,
                                            clipStartTime: timeRange.lowerBound,
                                            clipEndTime: timeRange.upperBound,
                                            videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                                            videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                                        )
                                    )
                                )

                            case .confirmSelectionTapped:
                                track(
                                    Event(
                                        category: .hooksCreate,
                                        actionName: .snippetSelectionSaveTapped,
                                        actionType: .tap,
                                        elementType: .button,
                                        elementId: before.clip.id.remoteId,
                                        context: createContext(
                                            videoId: before.selectedMovie?.url.absoluteString,
                                            clipId: before.clip.id.remoteId,
                                            clipVolumeLevel: before.snippetVolumeLevel,
                                            videoVolumeLevel: before.videoVolumeLevel,
                                            clipStartTime: before.snippetStartTime,
                                            clipEndTime: before.snippetEndTime,
                                            videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                                            videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                                        )
                                    )
                                )

                            case .backTapped:
                                track(
                                    Event(
                                        category: .hooksCreate,
                                        actionName: .snippetSelectionCancelTapped,
                                        actionType: .tap,
                                        elementType: .button,
                                        elementId: before.clip.id.remoteId,
                                        context: createContext(
                                            videoId: before.selectedMovie?.url.absoluteString,
                                            clipId: before.clip.id.remoteId,
                                            clipVolumeLevel: before.snippetVolumeLevel,
                                            videoVolumeLevel: before.videoVolumeLevel,
                                            clipStartTime: before.snippetStartTime,
                                            clipEndTime: before.snippetEndTime,
                                            videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                                            videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                                        )
                                    )
                                )

                            default:
                                break
                            }
                        default:
                            break
                        }
                    default:
                        break
                    }
                default:
                    break
                }

            case .destination(let destinationAction):
                switch destinationAction {
                case .presented(let presentedAction):
                    switch presentedAction {
                    case .volumeSelector(let volumeSelectorAction):
                        switch volumeSelectorAction {
                        case .doneTapped:
                            track(
                                Event(
                                    category: .hooksCreate,
                                    actionName: .volumeSelectorSaveTapped,
                                    actionType: .tap,
                                    elementType: .button,
                                    elementId: before.selectedMovie?.url.absoluteString,
                                    context: createContext(
                                        videoId: before.selectedMovie?.url.absoluteString,
                                        clipId: before.clip.id.remoteId,
                                        clipVolumeLevel: before.snippetVolumeLevel,
                                        videoVolumeLevel: before.videoVolumeLevel,
                                        clipStartTime: before.snippetStartTime,
                                        clipEndTime: before.snippetEndTime,
                                        videoStartTime: (before.trimmedStartTime ?? before.originalStartTime),
                                        videoEndTime: (before.trimmedEndTime ?? before.originalEndTime)
                                    )
                                )
                            )
                        default:
                            break
                        }
                    default:
                        break
                    }
                default:
                    break
                }

            default:
                break
            }
            return .none
        }

        private func createContext(videoId: String? = nil, clipId: String? = nil, clipVolumeLevel: Float? = nil, videoVolumeLevel: Float? = nil, clipStartTime: Double? = nil, clipEndTime: Double? = nil, videoStartTime: Double? = nil, videoEndTime: Double? = nil) -> String {
            return HooksEditorAnalyticsContext(
                videoId: videoId,
                clipId: clipId,
                clipVolumeLevel: clipVolumeLevel,
                videoVolumeLevel: videoVolumeLevel,
                clipStartTime: clipStartTime,
                clipEndTime: clipEndTime,
                videoStartTime: videoStartTime,
                videoEndTime: videoEndTime
            )
            .toJsonString()
        }
    }
}

private struct HooksEditorAnalyticsContext: Codable {
    public var videoId: String?
    public var clipId: String?
    public var clipVolumeLevel: Float?
    public var videoVolumeLevel: Float?
    public var clipStartTime: Double?
    public var clipEndTime: Double?
    public var videoStartTime: Double?
    public var videoEndTime: Double?

    public init(
        videoId: String?,
        clipId: String?,
        clipVolumeLevel: Float?,
        videoVolumeLevel: Float?,
        clipStartTime: Double?,
        clipEndTime: Double?,
        videoStartTime: Double?,
        videoEndTime: Double?
    ) {
        self.videoId = videoId
        self.clipId = clipId
        self.clipVolumeLevel = clipVolumeLevel
        self.videoVolumeLevel = videoVolumeLevel
        self.clipStartTime = clipStartTime
        self.clipEndTime = clipEndTime
        self.videoStartTime = videoStartTime
        self.videoEndTime = videoEndTime
    }

    public func toJsonString() -> String {
        guard let data = try? JSONEncoder().encode(self),
              let jsonString = String(data: data, encoding: .utf8)
        else {
            return "{}"
        }
        return jsonString
    }
}
