import AnalyticsClient
import APIClient
import ComposableArchitecture
import FeatureHooksCamera
import FeatureHooksPost
import FeatureHooksSongPicker
import FeatureToasts
import FeatureVideoTrimmer
import Foundation
import Localization
import NavigationRouterClient
import SnippetPlayerClient
import StatsigClient
import SwiftUI
import Utilities

// swiftlint:disable trailing_whitespace

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

enum Constants {
    static let defaultMaxRecordingLimit: TimeInterval = 120.0
    static let defaultMinRecordingLimit: TimeInterval = 10.0
}

@Reducer
public struct HooksCreateCoordinator {
    @Reducer(state: .equatable)
    public enum Path {
        case songPicker(HooksSongPicker)
        case camera(HooksEditor)
        case hooksPost(HooksPostScreenReducer)
    }

    @ObservableState
    public struct State: Equatable {
        @Shared var me: Me
        // Initialize this in the init to prevent crash
        @ObservationStateIgnored @ObservedBox public var songPicker: HooksSongPicker.State
        @Shared(.inMemory(.isOnHooksFeed)) var isOnHooksFeed: Bool = false

        /* Tracked between steps */
        var selectedClip: Clip?
        var selectedStartTime: Double = .zero
        var selectedEndTime: Double?
        var selectedPostVideoAssetURL: URL?

        var originalVideoAssetUrl: URL?
        var originalVideoTrimStartSeconds: Double?
        var originalVideoTrimEndSeconds: Double?

        var capturedThumbnail: UIImage?

        var videoVolumeLevel: Float = 0.5
        var snippetVolumeLevel: Float = 1.0

        var videoDurationLimits: (TimeInterval, TimeInterval) {
            @Dependency(StatsigClient.self) var statsigClient
            let config = statsigClient.getDynamicConfig(
                .hooksVideoUploadDuration,
                as: HooksVideoUploadDurationConfig.self
            )

            return (config.map { TimeInterval($0.minSeconds) } ?? Constants.defaultMinRecordingLimit,
                    config.map { TimeInterval($0.maxSeconds) } ?? Constants.defaultMaxRecordingLimit)
        }

        func createPostingData() -> HooksPostingData? {
            guard let selectedClip, let selectedPostVideoAssetURL, let selectedEndTime else {
                if selectedClip == nil {
                    log.telemetry.assertionFailure("createPostingData failed: selectedClip is nil")
                }
                if selectedPostVideoAssetURL == nil {
                    log.telemetry.assertionFailure("createPostingData failed: selectedPostVideoAssetURL is nil")
                }
                if selectedEndTime == nil {
                    log.telemetry.assertionFailure("createPostingData failed: selectedEndTime is nil")
                }
                return nil
            }

            let clipEndTime = min(selectedEndTime, selectedStartTime + selectedClip.duration)

            return HooksPostingData(
                selectedClip: selectedClip,
                selectedClipStartTime: selectedStartTime,
                selectedClipEndTime: clipEndTime,
                sourceVideoStartTime: originalVideoTrimStartSeconds ?? 0,
                sourceVideoEndTime: originalVideoTrimEndSeconds ?? 0,
                postVideoAssetLocalURL: selectedPostVideoAssetURL
            )
        }

        var canProceedToPost: Bool {
            selectedClip != nil && selectedPostVideoAssetURL != nil
        }

        public var path: StackState<Path.State> = .init()
        public init(me: Shared<Me>, initialClip: Clip? = nil, initialLocalVideoURL: URL? = nil) {
            self._me = me
            self.songPicker = .init()
            let videoDurationLimits = self.videoDurationLimits
            
            if let clip = initialClip, let url = initialLocalVideoURL {
                self.selectedClip = clip
                self.selectedPostVideoAssetURL = url
                self.selectedStartTime = 0
                self.selectedEndTime = min(30.0, clip.duration)
                self.path.append(.camera(.init(
                    clip: clip,
                    snippetStartTime: self.selectedStartTime,
                    snippetEndTime: self.selectedEndTime ?? min(30.0, clip.duration),
                    initialLocalVideoURL: url,
                    minVideoUploadDuration: videoDurationLimits.0,
                    maxVideoUploadDuration: videoDurationLimits.1
                )))
            }
        }
    }

    public enum Action {
        @CasePathable
        @dynamicMemberLookup
        public enum Internal {
            case goToSongPicker
            case goToCamera
            case goToPost
        }

        @CasePathable
        @dynamicMemberLookup
        public enum Delegate {
            case hookCreatedSuccessfully(hookId: String)
            case dismiss(resumeHook: Bool, resumeMusic: Bool)
        }

        public enum Analytics {
            case onboardingFlowStarted
        }

        case task
        case path(StackAction<Path.State, Path.Action>)
        case songPicker(HooksSongPicker.Action)
        case analytics(Analytics)
        case delegate(Delegate)
        case `internal`(Internal)
    }

    @Dependency(\.apiClientV2) var api
    @Dependency(\.telemetryClient) var telemetry
    @Dependency(\.snippetPlayerClient) var snippetPlayerClient
    @Dependency(\.hooksPostClient) var hooksPostClient
    @Dependency(\.toastClient.show) var showToast
    @Dependency(\.dismiss) var dismiss

    public init() {}

    public var body: some ReducerOf<Self> {
        Scope(state: \.songPicker, action: \.songPicker) {
            HooksSongPicker()
        }
        Reduce { state, action in
            switch action {
            case .task:
                snippetPlayerClient.setup(repeats: true)
                return .none

            case .path(.element(_, .camera(.delegate(.didCompleteCaptureAndCompose(let localAssetURL, let trimmedStartTime, let trimmedEndTime, let snippetStartTime, let snippetEndTime, let hookThumbnail, let videoVolumeLevel, let snippetVolumeLevel))))):
                state.selectedPostVideoAssetURL = localAssetURL
                state.originalVideoTrimStartSeconds = trimmedStartTime
                state.originalVideoTrimEndSeconds = trimmedEndTime
                state.capturedThumbnail = hookThumbnail
                // Update the selected times with the snippet selection
                state.selectedStartTime = snippetStartTime
                state.selectedEndTime = snippetEndTime
                state.videoVolumeLevel = videoVolumeLevel
                state.snippetVolumeLevel = snippetVolumeLevel

                // Go directly to post after capture
                return .send(.internal(.goToPost))

            case .path(.element(_, .camera(.delegate(.videoUploadCompleted(_, _))))):
                return .none

            case .internal(.goToSongPicker):
                state.path.append(.songPicker(.init()))
                return .none

            case .songPicker(.delegate(.didSelectSnippetAndMovie(let snippet, let movieURL))):
                state.selectedClip = snippet.clip
                state.selectedStartTime = snippet.startTime
                state.selectedEndTime = snippet.endTime
                state.originalVideoTrimStartSeconds = nil
                state.originalVideoTrimEndSeconds = nil
                let videoDurationLimits = state.videoDurationLimits
                state.path.append(.camera(.init(
                    clip: snippet.clip,
                    snippetStartTime: snippet.startTime,
                    snippetEndTime: snippet.endTime,
                    initialLocalVideoURL: movieURL,
                    minVideoUploadDuration: videoDurationLimits.0,
                    maxVideoUploadDuration: videoDurationLimits.1
                )))
                return .none

            case .path(.element(_, .songPicker(.delegate(.didSelectSnippetAndMovie(let snippet, let movieURL))))):
                state.selectedClip = snippet.clip
                state.selectedStartTime = snippet.startTime
                state.selectedEndTime = snippet.endTime
                state.originalVideoTrimStartSeconds = nil
                state.originalVideoTrimEndSeconds = nil
                let videoDurationLimits = state.videoDurationLimits
                state.path.append(.camera(.init(
                    clip: snippet.clip,
                    snippetStartTime: snippet.startTime,
                    snippetEndTime: snippet.endTime,
                    initialLocalVideoURL: movieURL,
                    minVideoUploadDuration: videoDurationLimits.0,
                    maxVideoUploadDuration: videoDurationLimits.1
                )))
                return .none

            case .path(.element(_, .hooksPost(.delegate(.backTapped)))):
                state.path.removeLast()
                return .none

            case .songPicker(.delegate(.dismiss)),
                 .path(.element(_, .camera(.delegate(.dismiss)))):
                snippetPlayerClient.teardown()
                let resumeHook = state.isOnHooksFeed == true
                let resumeMusic = state.isOnHooksFeed == false
                return .send(.delegate(.dismiss(resumeHook: resumeHook, resumeMusic: resumeMusic)))

            case .internal(.goToPost):
                guard let postingData = state.createPostingData() else {
                    showToast(
                        ToastReducer.State.ToastType.warning(
                            L10n.FeatureHooks.somethingWentWrong,
                            position: .bottom
                        )
                    )
                    return .none
                }

                let hooksPostState = HooksPostScreenReducer.State(
                    clipId: postingData.selectedClip.id,
                    clipStartTime: postingData.selectedClipStartTime,
                    clipEndTime: postingData.selectedClipEndTime,
                    sourceVideoStartTime: postingData.sourceVideoStartTime,
                    sourceVideoEndTime: postingData.sourceVideoEndTime,
                    localVideoURL: postingData.postVideoAssetLocalURL,
                    videoVolumeLevel: state.videoVolumeLevel,
                    snippetVolumeLevel: state.snippetVolumeLevel,
                    me: state.$me
                )

                state.path.append(.hooksPost(hooksPostState))
                return .none

            case .path(.element(_, .hooksPost(.delegate(.postTapped(let clipId, let clipStartTime, let clipEndTime, let sourceVideoStartTime, let sourceVideoEndTime, let caption, let videoVolumeLevel, let snippetVolumeLevel, let showLyrics, let allowComments))))):
                let thumbnailImage = state.capturedThumbnail
                let isOnHooksFeed = state.isOnHooksFeed
                return .merge(
                    .run { _ in
                        do {
                            try await hooksPostClient.createHook(
                                clipId,
                                clipStartTime,
                                clipEndTime,
                                sourceVideoStartTime,
                                sourceVideoEndTime,
                                caption,
                                thumbnailImage,
                                videoVolumeLevel,
                                snippetVolumeLevel,
                                showLyrics,
                                allowComments
                            )
                        } catch {
                            // Log error but don't propagate since this is a fire-and-forget operation
                            log.telemetry.error(error, message: "Failed to create hook.")
                        }
                    },
                    .run { _ in snippetPlayerClient.teardown() },
                    .run { send in
                        @Dependency(NavigationRouterClient.self) var navigationRouterClient
                        navigationRouterClient.send(.hooksFeed)
                        await send(.delegate(.dismiss(resumeHook: isOnHooksFeed, resumeMusic: false)))
                    }
                )

            case .delegate,
                 .analytics,
                 .internal,
                 .songPicker,
                 .path:
                return .none
            }
        }
        .forEach(\.path, action: \.path)
        Analytics()
    }
}

public struct HooksCreateView: View {
    @Bindable var store: StoreOf<HooksCreateCoordinator>

    public init(store: StoreOf<HooksCreateCoordinator>) {
        self.store = store
    }

    public var body: some View {
        NavigationStack(
            path: $store.scope(state: \.path, action: \.path),
            root: {
                if store.path.isEmpty {
                    HooksSongPickerView(store: store.scope(state: \.songPicker, action: \.songPicker))
                } else {
                    EmptyView()
                }
            },
            destination: { path in
                switch path.case {
                case .songPicker(let store):
                    HooksSongPickerView(store: store)
                        .toolbar(.hidden, for: .navigationBar)

                case .camera(let store):
                    HooksEditorView(store: store)
                        .toolbar(.hidden, for: .navigationBar)
                        .navigationSwipeDisabled(navigationBarHidden: true)

                case .hooksPost(let store):
                    HooksPostScreen(store: store)
                        .toolbar(.hidden, for: .navigationBar)
                }
            }
        )
        .task { store.send(.task) }
        .environment(\.colorScheme, .dark)
    }
}
