import AdamantiumClient
import AnalyticsClient
import APIClient
import AVFoundation
import AVKit
import ComponentLibrary
import ComposableArchitecture
import DeeplinkIntents
import FeatureRatingTracker
import FeatureToasts
import Localization
import SwiftUI
import Utilities

// swiftlint:disable file_length

@Reducer
public struct Share {
    public enum ShareError: Error {
        case tiktokShareResult(String)
    }

    @ObservableState
    public struct State: Equatable {
        public enum ShareType: Equatable {
            case profile(Profile)
            case me(User)
            case clip(Clip)
            case playlist(Playlist)
            case genre(Genre, [Clip])

            public var imageURL: String? {
                switch self {
                case .profile(let profile): profile.avatarImageUrl
                case .me(let user): user.avatarImageUrl
                case .clip(let clip): clip.largeImageUrl
                case .playlist(let playlist): playlist.imageUrl
                case .genre(let genre, _): genre.image
                }
            }

            public var clipUrl: String? {
                switch self {
                case .profile(let profile): profile.clips.first?.audioUrl
                case .me: nil
                case .clip(let clip): clip.audioUrl
                case .playlist(let playlist): playlist.clips.first?.audioUrl
                case .genre(_, let topClips): topClips.first?.audioUrl
                }
            }

            public var pageTitle: String {
                switch self {
                case .profile: L10n.FeatureShare.shareProfileTitle
                case .me: L10n.FeatureShare.shareMeTitle
                case .clip: L10n.FeatureShare.shareSongTitle
                case .playlist: L10n.FeatureShare.sharePlaylistTitle
                case .genre: L10n.FeatureShare.shareStyleTitle
                }
            }

            public var subject: String {
                switch self {
                case .profile: L10n.FeatureShare.shareProfileSubject
                case .me: L10n.FeatureShare.shareMeSubject
                case .clip: L10n.FeatureShare.shareSongSubject
                case .playlist: L10n.FeatureShare.sharePlaylistSubject
                case .genre: L10n.FeatureShare.shareStyleSubject
                }
            }

            public var message: String? {
                switch self {
                case .profile: L10n.FeatureShare.shareProfileMessage
                case .me: nil
                case .clip: L10n.FeatureShare.shareSongMessage
                case .playlist: L10n.FeatureShare.sharePlaylistMessage
                case .genre: L10n.FeatureShare.shareStyleMessage
                }
            }

            public var subtitle: String {
                switch self {
                case .profile(let profile): "@\(profile.handle)"
                case .me(let user): "@\(user.handle)"
                case .clip(let clip): clip.tags.capitalized
                case .playlist(let playlist): L10n.FeatureShare.shareSongs(playlist.clips.count)
                case .genre(let genre, _): L10n.FeatureShare.shareSongs(genre.totalClipsCount)
                }
            }

            public var toolbarButtonBackground: AnyShapeStyle {
                switch self {
                case .clip: AnyShapeStyle(Material.ultraThin)
                case .profile: AnyShapeStyle(Color.SemanticV1.backgroundQuaternary)
                case .me: AnyShapeStyle(Color.SemanticV1.backgroundQuaternary)
                case .playlist: AnyShapeStyle(Material.ultraThin)
                case .genre: AnyShapeStyle(Material.ultraThin)
                }
            }

            public func auraVideoFileURL(_ adamantiumClient: AdamantiumClient) -> URL? {
                switch self {
                case .clip, .playlist:
                    return adamantiumClient.getCacheResource(.videoAuraLoopRedOrangeYellowV1)
                case .profile, .me:
                    return adamantiumClient.getCacheResource(.videoAuraLoopPurpleOrangeV1)
                case .genre:
                    return adamantiumClient.getCacheResource(.videoAuraLoopRedOrangeYellowV1)
                }
            }

            public var toastWidth: CGFloat {
                return .infinity
            }
        }

        @Shared var me: Me
        var shareType: ShareType
        var stickerData: Data = .init()
        var videoURL: URL?
        var watermarkedVideoURL: URL?
        var capturedShot: UIImage?
        var shareDestinations: [ShareDestination] = []
        var compactShareDestinations: [ShareDestination] = []
        var pendingShareDestination: ShareDestination?
        @ObservationStateIgnored @ObservedBox var toastState = ToastReducer.State()
        @ObservationStateIgnored @ObservedBox var ratingTrackerState = RatingTracker.State()

        var showLyrics = false

        var mediaReady: Bool {
            videoURL != nil && !stickerData.isEmpty
        }

        var isShareInProgress = false
        var isWatermarkInProgress = false
        var backgroundRefreshProxy: Int = 0

        public init(_ shareType: ShareType, me: Shared<Me>) {
            self.shareType = shareType
            self._me = me

            self.shareDestinations = ShareDestination.getShareDestinations()
            self.compactShareDestinations = ShareDestination.getCompactShareDestinations()
        }
    }

    public enum Action: BindableAction {
        case task
        case dismiss
        case captureShot(UIImage)
        case toastAction(ToastReducer.Action)
        case pngImage(Data)
        case shareTapped(ShareDestination)
        case videoResponse(Result<URL?, Error>)
        case updateWatermarkedVideo(URL?)
        case binding(BindingAction<State>)
        case onAppear
        case ratingTrackerAction(RatingTracker.Action)
        case handleURL(URL)
        case shareResponse(Result<Void, Error>)

        case setShowLyrics(Bool)

        case `internal`(Internal)

        public enum Internal {
            case incrementActionResponse(Result<Void, Error>)
        }
    }

    @Dependency(\.dismiss) var dismiss
    @Dependency(\.openURL) var openURL
    @Dependency(AnalyticsClient.self) var analyticsClient
    @Dependency(AdamantiumClient.self) var adamantiumClient
    @Dependency(APIClient.self) var apiClient
    @Dependency(APIClientV2.self) var api
    @Dependency(\.telemetryClient) var telemetry

    public init() {}

    public var body: some ReducerOf<Self> {
        Scope(state: \.ratingTrackerState, action: \.ratingTrackerAction) {
            RatingTracker()
        }
        Scope(state: \.toastState, action: \.toastAction) {
            ToastReducer()
        }
        BindingReducer()
        Reduce<State, Action> { state, action in
            switch action {
            case .shareTapped(let socialShare):
                guard !state.isShareInProgress else { return .none }
                var effects: [Effect<Action>] = []

                if state.toastState.toast != nil {
                    effects.append(.send(.toastAction(.dismiss)))
                }

                state.isShareInProgress = true

                var isAuthorOfClip = false
                switch state.shareType {
                case .clip(let clip):
                    isAuthorOfClip = state.me.user.id == clip.userId
                    effects.append(.run { send in
                        let action = socialShare.isDownloadEvent ? APIClient.IncrementableAction.downloadVideo : APIClient.IncrementableAction.share
                        await send(.internal(.incrementActionResponse(.init(catching: { try await apiClient.incrementAction(clip, action) }))))
                    })
                    analyticsClient.track(.audioAction(.shareSongTapped(shareType: socialShare.name), clip: clip))

                case .me:
                    analyticsClient.track(.general(.inviteShareTapped(shareType: socialShare.name)))

                default:
                    break
                }

                effects.append(.send(.ratingTrackerAction(.share)))

                effects.append(.run { [
                    shareType = state.shareType,
                    stickerData = state.stickerData,
                    videoURL = state.videoURL,
                    watermarkedVideoURL = state.watermarkedVideoURL,
                    isAuthorOfClip = isAuthorOfClip
                ] send in
                    let shareURL = await fetchShareURL(shareType: shareType, destination: socialShare)
                    do {
                        let toastMessage = try await socialShare.share(.init(
                            shareURL: shareURL,
                            stickerData: stickerData,
                            videoURL: videoURL,
                            downloadableURL: watermarkedVideoURL,
                            subject: shareType.subject,
                            message: shareType.message,
                            isAuthorOfClip: isAuthorOfClip
                        ))

                        await send(.shareResponse(.success({}())))
                        if let toastMessage {
                            await send(.toastAction(.show(.success(toastMessage, position: .bottom))))
                        }
                    } catch {
                        await send(.shareResponse(.failure(error)))
                        await send(.toastAction(.show(.warning(L10n.FeatureShare.sharingFailed, position: .bottom))))
                    }
                })

                return .concatenate(effects)

            case .shareResponse(.success):
                state.isShareInProgress = false
                state.pendingShareDestination = nil
                return .none

            case let .shareResponse(.failure(error)):
                log.telemetry.error(error)
                state.isShareInProgress = false
                state.pendingShareDestination = nil
                return .none

            case .updateWatermarkedVideo(let url):
                state.isWatermarkInProgress = true
                state.watermarkedVideoURL = url
                guard
                    let destination = state.pendingShareDestination,
                    destination.requiresWatermark
                else { return .none }
                state.pendingShareDestination = nil
                return .run { send in
                    await UINotificationFeedbackGenerator().notificationOccurred(.success)
                    await send(.shareTapped(destination))
                }

            case let .videoResponse(.success(url)):
                state.videoURL = url
                let runWatermarkingEffect: Effect<Action> = .run { [shareType = state.shareType] send in
                    let newWatermarkedVideoURL = try await adamantiumClient
                        .watermarkVideo(url, Image.RenderingAsset.watermarkStillV1, false)
                    await send(.updateWatermarkedVideo(newWatermarkedVideoURL))
                }

                guard
                    let destination = state.pendingShareDestination,
                    !destination.requiresWatermark,
                    !state.isWatermarkInProgress
                else {
                    state.isWatermarkInProgress = true
                    return runWatermarkingEffect
                }

                state.pendingShareDestination = nil
                return .run { send in
                    await send(.shareTapped(destination))
                }

            case let .videoResponse(.failure(error)):
                log.telemetry.error(error)
                return .none

            case .captureShot(let image):
                state.capturedShot = image
                return .run { send in
                    if let data = image.pngData() {
                        await send(.pngImage(data))
                    }
                }

            case .pngImage(let data):
                state.stickerData = data
                return .none

            case .dismiss:
                if case .clip(let clip) = state.shareType {
                    analyticsClient.track(
                        .audioAction(
                            .shareSongScreenClosed,
                            clip: clip
                        )
                    )
                }

                return .run { _ in await self.dismiss() }

            case .task:
                return .none

            case .onAppear:
                state.backgroundRefreshProxy += 1
                if case .clip(let clip) = state.shareType {
                    analyticsClient.track(
                        .audioAction(
                            .shareSongScreenOpened,
                            clip: clip
                        )
                    )
                }
                return .none

            case .setShowLyrics(let showLyrics):
                state.showLyrics = showLyrics
                return .none

            case .internal(.incrementActionResponse(.failure(let error))):
                log.telemetry.error(error)
                return .none

            case .handleURL(let url):
                guard let route = DeeplinkIntent(url: url) else {
                    return .none
                }
                switch route {
                case .tiktokShareResult(let status):
                    switch status {
                    case .sharedSuccessfully:
                        return .merge(
                            .run { send in
                                await send(.toastAction(.show(.success(L10n.FeatureShare.sharingSucceeded, position: .bottom))))
                            },
                            .send(.shareResponse(.success({}())))
                        )

                    case .sharingCancelled:
                        return .send(.shareResponse(.success({}())))

                    case .sharingError(reason: let reason):
                        return .merge(
                            .run { send in
                                await send(.toastAction(.show(.warning(L10n.FeatureShare.sharingFailed, position: .bottom))))
                            },
                            .send(.shareResponse(.failure(ShareError.tiktokShareResult(reason))))
                        )
                    }

                default:
                    return .none
                }

            case .binding, .toastAction, .ratingTrackerAction, .internal:
                // Catch-all
                return .none
            }
        }
    }

    private func fetchShareURL(shareType: State.ShareType, destination: ShareDestination) async -> URL {
        switch shareType {
        case .profile(let profile): return profile.shareURL

        case .me(let user): return user.shareURL

        case .playlist(let playlist): return playlist.shareURL

        case .genre(let genre, _): return genre.shareURL

        case .clip(let clip):
            do {
                return try await self.api.postShareLink(
                    contentId: clip.id.remoteId,
                    contentType: "song",
                    platform: destination.platformName,
                    source: "ios",
                    recommendationMetadata: nil
                ).url
            } catch {
                log.telemetry.error(error)
                return clip.fallbackShareURL
            }
        }
    }
}

public struct ShareScreen: View {
    @Bindable var store: StoreOf<Share>
    @Namespace private var namespace
    @State private var detentHeight: CGFloat = 0
    let columns = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]

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

    public var body: some View {
        VStack(spacing: 0) {
            navBar
            Spacer()
            shareContent
                .screenshotView { @MainActor screenshotTool in
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                        guard store.capturedShot == nil, let image = screenshotTool.screenshot() else { return }
                        store.send(.captureShot(image))
                    }
                }
                .environment(\.colorScheme, .light)
                .overlay(alignment: .bottom) {
                    ToastView(store: store.scope(state: \.toastState, action: \.toastAction))
                        .frame(maxWidth: store.shareType.toastWidth)
                }
            LazyVGrid(columns: columns, alignment: .center, spacing: 16) {
                let shareDestinations = UIScreen.isCompact ? store.compactShareDestinations : store.shareDestinations
                ForEach(shareDestinations) { destination in
                    ShareButtonView(destination) {
                        store.send(.shareTapped(destination))
                    }
                }
            }
            .padding(.horizontal, 36)
            .padding(.top, store.shareDestinations.count > 4 ? 40 : 60)
            .foregroundColor(Color.SemanticV1.backgroundPrimary)
            Spacer()
        }
        .background(aura)
        .task {
            store.send(.task)
        }
        .onGeometryChange(for: CGSize.self) { proxy in proxy.size } action: {
            detentHeight = $0.height + 44 // the height of the navigation bar and bottom padding
        }
        .presentationCornerRadius(40, conditional: true)
        .presentationDetents([.height(detentHeight)])
        .onAppear {
            store.send(.onAppear)
        }
        .onOpenURL { url in
            store.send(.handleURL(url))
        }
    }

    @ViewBuilder
    var aura: some View {
        switch store.shareType {
        case .me, .profile:
            ZStack {
                ShareAuraView()
                    .id(store.backgroundRefreshProxy)
                LinearGradient(
                    stops: [
                        Gradient.Stop(color: Color.SemanticV1.backgroundInvert.opacity(0.2), location: 0.00),
                        Gradient.Stop(color: Color.SemanticV1.backgroundInvert.opacity(0.65), location: 1.00),
                    ],
                    startPoint: UnitPoint(x: 0.5, y: -0.05),
                    endPoint: UnitPoint(x: 0.5, y: 0.6)
                )
                .environment(\.colorScheme, .light)
            }
            .ignoresSafeArea()

        default:
            ShareAuraView()
                .id(store.backgroundRefreshProxy)
                .ignoresSafeArea()
        }
    }

    @ViewBuilder
    var navBar: some View {
        ZStack {
            Text(store.shareType.pageTitle)
                .typographyV1(.navigationTitle)
                .foregroundColor(Color.SemanticV1.textOnDark)
                .frame(maxWidth: .infinity, alignment: .center)
            ToolbarButton(.close, background: Material.ultraThin) {
                store.send(.dismiss)
            }
            .frame(maxWidth: .infinity, alignment: .trailing)
            .environment(\.colorScheme, .dark)
        }
        .padding(.horizontal, 24)
        .padding(.top, 24)
    }

    @ViewBuilder
    var shareContent: some View {
        switch store.shareType {
        case .clip(let clip): SongShareCardView(clip: clip, me: store.$me)
        case .profile(let profile): ProfileShareCardView(.profile(profile), me: store.$me)
        case .me(let user): InviteShareCardView(avatarUrl: user.avatarImageUrl, avatarFallbackId: user.id)
        case .playlist(let playlist): PlaylistShareCardView(playlist, me: store.$me)
        case .genre(let genre, let topClips): GenreShareCardView(genre: genre, topClips: topClips)
        }
    }
}

private struct ShareButtonView: View {
    let social: ShareDestination
    let action: () -> Void

    init(_ socialShare: ShareDestination, action: @escaping () -> Void) {
        self.action = action
        self.social = socialShare
    }

    var body: some View {
        Button {
            UIImpactFeedbackGenerator(style: .light).impactOccurred()
            action()
        } label: {
            VStack(alignment: .center, spacing: 4) {
                Circle()
                    .fill(Color.SemanticV1.iconOnDark.opacity(0.15))
                    .frame(width: 48, height: 48)
                    .overlay {
                        social.icon
                            .foregroundColor(.SemanticV1.iconOnDark)
                    }
                Text(social.name)
                    .foregroundColor(Color.SemanticV1.textOnDark)
                    .typographyV1(.caption3)
            }
            .frame(maxHeight: .infinity, alignment: .top)
        }
        .buttonStyle(ScaleButtonStyle(scaleAmount: 0.95))
        .clipShape(.rect)
    }
}
