import APIClient
import ComponentLibrary
import ComposableArchitecture
import CoreMedia
import FeatureToasts
import Localization
import LyricsClient
import PlayerClient
import ShareAssetClient
import StatsigClient
import SwiftUI
import Utilities

// swiftlint:disable file_length

@Reducer
public struct AssetDownloadScreenReducer {
    @ObservableState
    public struct State: Equatable {
        let clip: Clip
        var clipDuration: CMTime
        let triggerID: String
        let shareURL: URL
        let assetTarget: ShareAssetCreationTarget
        var renderedAssetURL: URL?
        var presetConfig: VisualizerAssetConfig
        let lyricsData: LyricsDataV2
        let startTime: TimeInterval
        let endTime: TimeInterval

        @ObservationStateIgnored @ObservedBox var toast = ToastReducer.State()

        let possibleShareTargets: [ShareAssetCreationTarget] = [
            .downloadVideo, .facebookStories, .instagramStories, .tiktokVideo,
        ]

        public init(
            clip: Clip,
            triggerID: String,
            shareURL: URL,
            assetTarget: ShareAssetCreationTarget,
            presetConfig: VisualizerAssetConfig,
            lyricsData: LyricsDataV2,
            startTime: TimeInterval,
            endTime: TimeInterval
        ) {
            self.clip = clip
            self.triggerID = triggerID
            self.shareURL = shareURL
            self.assetTarget = assetTarget
            self.presetConfig = presetConfig
            self.lyricsData = lyricsData
            self.startTime = startTime
            self.endTime = endTime
            self.clipDuration = CMTime(seconds: clip.duration, preferredTimescale: 1000)
        }
    }

    public enum Action {
        case setup
        case toast(ToastReducer.Action)
        case onAppear
        case playSong
        case downloadSong
        case sendToTarget(_ target: ShareAssetCreationTarget)

        case saveClipDuration(CMTime)
        case setAudioPlayTime(TimeInterval)
        case shareAssetEvent(ShareAssetClient.ShareAssetClientEvent)
    }

    @Dependency(PlayerClient.self) var playerClient
    @Dependency(\.shareAssetClient) var shareAssetClient
    @Dependency(\.shareAssetClient.stream) var shareAssetEventStream

    public init() {}

    public var body: some ReducerOf<Self> {
        Scope(state: \.toast, action: \.toast) {
            ToastReducer()
        }

        Reduce { state, action in
            struct ShareAssetEventStreamCancellable: Hashable {}

            switch action {
            case .onAppear:
                return .send(.setup)

            case .setup:
                return .stream(
                    shareAssetEventStream(),
                    send: Action.shareAssetEvent,
                    cancellableId: ShareAssetEventStreamCancellable()
                )

            case .downloadSong:
                guard let renderedAssetURL = state.renderedAssetURL else { return .none }
                return .run { [renderedAssetURL] send in
                    await shareAssetClient.saveToDownloadPhotos(renderedAssetURL)
                    await send(.toast(.show(.success(L10n.FeatureShareAssets.savedShareableToPhotos))))
                }

            case .sendToTarget(let target):
                guard let renderedAssetURL = state.renderedAssetURL else { return .none }
                shareAssetClient.shareToDestination(
                    shareURL: state.shareURL,
                    videoURL: renderedAssetURL,
                    pngStickerData: .init(),
                    linkMessage: L10n.FeatureShare.shareSongSubject,
                    shareDestination: target
                )
                return .none

            case .playSong:
                let url = state.clip.playableSceneUrl?.absoluteString ?? state.clip.audioUrl
                return .run { send in
                    let duration = await playerClient.replaceCurrentItemAndPlay(url)
                    await send(.saveClipDuration(duration))
                }

            case .saveClipDuration(let time):
                state.clipDuration = time
                return .none

            case .setAudioPlayTime(let time):
                let cmTime = CMTime(seconds: time, preferredTimescale: 1000)
                return .run { _ in
                    playerClient.seek(cmTime)
                }

            case .shareAssetEvent(let event):
                switch event {
                case .startedRenderingShareAsset:
                    /*
                        Will probably not hit this here since share asset creation
                        is triggered before this download page is shown
                     */
                    break

                case .polledRendering:
                    /*
                        This will be hit a few times before render on
                        generating an asset.
                     */
                    break

                case .shareAssetReady(_, _, let assetTarget, _, let localAssetURL, let attributionURL, let triggerID):
                    /*  On rendering complete of rendering this will be triggered. */
                    guard triggerID == state.triggerID else { return .none }
                    state.renderedAssetURL = localAssetURL
                    shareAssetClient.shareToDestination(
                        shareURL: attributionURL,
                        videoURL: localAssetURL,
                        pngStickerData: .init(),
                        linkMessage: L10n.FeatureShare.shareSongSubject,
                        shareDestination: assetTarget
                    )

                case .shareAssetCouldNotRender:
                    /*  On rendering Fail this will be triggered. */
                    break
                }
                return .none

            case .toast:
                return .none
            }
        }
    }
}

public struct AssetDownloadScreenView: View {
    @Environment(\.dismiss) private var dismiss
    let store: StoreOf<AssetDownloadScreenReducer>
    @State var currentTime: TimeInterval = .zero
    @State var elapsedTime: TimeInterval = .zero

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

    struct CardSizeConfig {
        let width: CGFloat
        let height: CGFloat
        let cornerRadius: CGFloat
        let offset: CGFloat

        init(
            gallerySize: CGSize
        ) {
            let newHeight = max(gallerySize.height * 0.85, 1)
            self.height = newHeight
            self.width = max(newHeight * (9.0 / 16.0), 1.0)
            self.cornerRadius = max(newHeight * 0.025, 1.0)
            self.offset = 16.0
        }
    }

    public var body: some View {
        VStack(spacing: .zero) {
            headerToolbar

            previewSpace
                .overlay(alignment: .bottom) {
                    ToastView(store: store.scope(state: \.toast, action: \.toast))
                        .padding(.bottom, 8)
                }

            if store.renderedAssetURL == nil {
                InfiniteProgressBarView()
                    .frame(height: 4.0)
            }

            loadingFooterView
        }
        .presentationCornerRadius(40, conditional: true)
        .foregroundStyle(Color.SemanticV1.textPrimary)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.SemanticV1.backgroundPrimary)
        .colorScheme(.dark)
        .ignoresSafeArea(.container, edges: .bottom)
        .onAppear {
            store.send(.onAppear)
        }
    }
}

private extension AssetDownloadScreenView {
    @ViewBuilder
    var previewSpace: some View {
        Color.clear
            .overlay {
                GeometryReader { geoProxy in
                    ZStack(alignment: .center) {
                        Color.clear

                        let sizeConfig = CardSizeConfig(gallerySize: geoProxy.size)

                        if geoProxy.size == .zero {
                            Color.clear
                        } else {
                            VStack {
                                ZStack {
                                    if let renderedAssetURL = store.renderedAssetURL {
                                        LoopingVideoPlayer(
                                            playableUrl: renderedAssetURL,
                                            restartBeforePlaying: false,
                                            isPlaying: Binding(
                                                get: { true },
                                                set: { _ in }
                                            ),
                                            overrideTime: nil,
                                            videoGravity: .resizeAspectFill,
                                            fallbackView: {
                                                Color.black
                                            }
                                        )
                                        .frame(
                                            width: sizeConfig.width,
                                            height: sizeConfig.height
                                        )
                                    } else {
                                        VisualizerCompositeCard(
                                            config: store.presetConfig,
                                            lyrics: store.lyricsData,
                                            clipTitle: store.clip.title,
                                            clipAuthorHandle: store.clip.handle,
                                            startTime: store.startTime,
                                            endTime: store.endTime,
                                            elapsedTime: elapsedTime,
                                            clipDuration: store.clip.duration,
                                            isPlaying: true,
                                            currentTime: currentTime,
                                            isVisualInFocus: true,
                                            cardSize: .init(
                                                width: sizeConfig.width,
                                                height: sizeConfig.height
                                            ),
                                            onTimeUpdate: { _, deltaTime, _ in
                                                let duration = store.endTime - store.startTime
                                                let newElapsedTime = (elapsedTime + deltaTime).truncatingRemainder(dividingBy: duration)
                                                if newElapsedTime < elapsedTime {
                                                    store.send(.setAudioPlayTime(store.startTime))
                                                }
                                                elapsedTime = newElapsedTime
                                                currentTime = newElapsedTime + store.startTime
                                            }
                                        )
                                    }
                                }
                                .clipShape(.rect(cornerRadius: sizeConfig.cornerRadius))

                                if let _ = store.renderedAssetURL {
                                    VStack(spacing: .zero) {
                                        Text(L10n.FeatureShareAssets.boomYourClipsReady)
                                            .multilineTextAlignment(.center)
                                            .typographyV1(.caption2)
                                            .foregroundStyle(Color.SemanticV1.textSecondary)
                                            .padding(.top, 16.0)

                                        Text(L10n.FeatureShareAssets.downloadAndShareAway)
                                            .multilineTextAlignment(.center)
                                            .typographyV1(.caption3)
                                            .foregroundStyle(Color.SemanticV1.textTertiary)
                                            .padding(.top, 2.0)
                                    }
                                }
                            }
                        }
                    }
                }
            }
    }

    @ViewBuilder
    var headerToolbar: some View {
        if let _ = store.renderedAssetURL {
            Toolbar(
                L10n.FeatureShareAssets.downloadAndShare,
                leading: { EmptyView() },
                trailing: { toolbarDismissButton }
            )
        } else {
            Toolbar(
                L10n.FeatureShareAssets.creatingVideo,
                leading: { EmptyView() },
                trailing: { toolbarDismissButton }
            )
        }
    }

    var toolbarDismissButton: some View {
        ToolbarButton(
            .close,
            background: Color.SemanticV1.backgroundTertiary
        ) {
            dismiss()
        }
    }

    @ViewBuilder
    func targetButton(_ target: ShareAssetCreationTarget) -> some View {
        Button {
            store.send(.sendToTarget(target))
        } label: {
            ZStack {
                switch target {
                case .downloadVideo:
                    Image.Assets.blank
                        .resizable()
                        .aspectRatio(contentMode: .fill)

                case .facebookStories:
                    Image.ShareIcon.facebook
                        .resizable()
                        .aspectRatio(contentMode: .fill)

                case .instagramStories:
                    Image.ShareIcon.instagram
                        .resizable()
                        .aspectRatio(contentMode: .fill)

                case .tiktokVideo:
                    Image.ShareIcon.tiktok
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                }
            }
            .frame(width: 48, height: 48)
            .clipShape(.rect(cornerRadius: .infinity))
        }
    }

    var loadingFooterView: some View {
        ZStack {
            if let _ = store.renderedAssetURL {
                // Share Actions
                HStack(spacing: 8.0) {
                    ForEach(store.possibleShareTargets, id: \.self) { target in
                        if target == .downloadVideo {
                            downloadButton
                        } else {
                            targetButton(target)
                        }
                    }
                }
                .padding(.horizontal, 16)
                .padding(.bottom, 10)
                .background(Color.SemanticV1.backgroundSecondary)
                .ignoresSafeArea(.container, edges: .bottom)
            } else {
                HStack {
                    Spacer()
                    VStack(spacing: .zero) {
                        Text(L10n.FeatureShareAssets.yourVideoClipIsInTheWorks)
                            .typographyV1(.body3)
                            .foregroundStyle(Color.SemanticV1.textPrimary)

                        Text(L10n.FeatureShareAssets.renderingCopyII)
                            .typographyV1(.body3.size { _ in 12.0 })
                            .foregroundStyle(Color.SemanticV1.textSecondary)
                    }
                    Spacer()
                }
            }
        }
        .padding(.top, 32.0)
        .padding(.bottom, 48.0)
        .background(Color.SemanticV1.backgroundSecondary)
    }

    var downloadButton: some View {
        Button {
            store.send(.downloadSong)
        } label: {
            HStack {
                Spacer()
                Image.Icon.downloadRounded
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 24.0, height: 24.0)
                Text(L10n.FeatureShareAssets.download)
                    .typographyV1(.caption2)
                Spacer()
            }
            .foregroundStyle(Color.SemanticV1.textInvert)
            .frame(height: 48.0)
            .background {
                Color.SemanticV1.backgroundInvert
            }
            .clipShape(.rect(cornerRadius: .infinity))
        }
        .buttonStyle(.plain)
    }
}
