import APIClient
import ComposableArchitecture
import EventBusClient
import FeatureToasts
import Localization
import NavigationRouterClient
import Utilities

let log = Logger(category: "SongActionsClient")

@DependencyClient
public struct SongActionsClient {
    public var setLiked: (_ clip: Clip, _ liked: Bool, _ showToast: Bool, _ hook: Hook?, _ source: HooksFeedSource?) -> Void
    public var setDisliked: (_ clip: Clip, _ disliked: Bool) -> Void
    public var deleteClip: (_ clip: Clip, _ isTrashed: Bool) -> Void
    public var reportInappropriate: (_ clip: Clip) -> Void
    public var removeClipFromPlaylist: (_ clip: Clip, _ playlist: Playlist) -> Void
    public var toggleVideoCoverInHooksFeed: (_ clip: Clip, _ showVideoCover: Bool) -> Void
    public var goToProfile: (_ handle: String) -> Void
}

extension SongActionsClient: DependencyKey {
    public static let liveValue = Self(
        setLiked: { clip, liked, showToastOnSuccess, hook, source in
            var updatedClip = clip
            updatedClip.isLiked = liked
            @Dependency(\.eventBus.sendClipEvent) var sendClipEvent
            sendClipEvent(.toggledLike(updatedClip))
            Task {
                let oldClip = clip // Mainly for legibility
                /// Send the clip event optimistically
                @Dependency(\.eventBus.sendClipEvent) var sendClipEvent

                @Dependency(\.apiClientV2) var apiClientV2
                // Toast on both error and success
                @Dependency(\.toastClient.show) var showToast
                do {
                    var recommendationMetadata: HooksRecommendationMetadata?
                    if let hook = hook {
                        recommendationMetadata = HooksRecommendationMetadata(
                            contextType: source?.analyticsContext.contextType,
                            hookId: hook.id,
                            recommendationItemId: hook.recommendationItemId
                        )
                    }
                    try await apiClientV2.setReaction(updatedClip, liked, clip.isDisliked, recommendationMetadata)
                    let successMessage = liked ? L10n.FeatureClipDetail.addedToLikedSongs : L10n.FeatureClipDetail.songRemovedFromLikes
                    if showToastOnSuccess {
                        showToast(.success(successMessage))
                    }
                } catch {
                    log.telemetry.error(error)
                    let failureMessage = liked ? L10n.FeatureClipDetail.likedSongFail : L10n.FeatureClipDetail.unlikeSongFailed
                    showToast(.warning(failureMessage))
                    // FIXME: (JY) - This undo should be more granular. Either we should try to re-fetch the clip, or we need a way to undo this particular thing as a DAG. Probably resolved by ClipClient
                    sendClipEvent(.updateClip(oldClip))
                }
            }
        },
        setDisliked: { clip, disliked in
            Task {
                let oldClip = clip // Mainly for legibility
                var updatedClip = clip
                updatedClip.isDisliked = disliked
                /// Send the clip event optimistically
                @Dependency(\.eventBus.sendClipEvent) var sendClipEvent
                sendClipEvent(.updateClip(updatedClip))

                @Dependency(\.apiClientV2) var apiClientV2
                // Toast on both error and success
                @Dependency(\.toastClient.show) var showToast
                do {
                    try await apiClientV2.setReaction(updatedClip, clip.isLiked, disliked, nil)
                    showToast(.success("Marked as Not Interested"))
                } catch {
                    log.telemetry.error(error)
                    let failureMessage = disliked ? "Could not dislike song" : "Could not remove dislike"
                    showToast(.warning(failureMessage))
                    // FIXME: (JY) - This undo should be more granular. Either we should try to re-fetch the clip, or we need a way to undo this particular thing as a DAG. Probably resolved by ClipClient
                    sendClipEvent(.updateClip(oldClip))
                }
            }
        },
        deleteClip: { clip, isTrashed in
            Task {
                @Dependency(\.eventBus.sendClipEvent) var sendClipEvent
                @Dependency(\.apiClient) var apiClient
                @Dependency(\.toastClient.show) var showToast

                do {
                    try await apiClient.trashClip(clip, isTrashed)
                    sendClipEvent(.removeClip(clip))
                    let toast = ToastReducer.State.ToastType.success(
                        L10n.FeatureClipDetail.songDeleted,
                        position: .top,
                        trailingView: .undo
                    )
                    showToast(toast)
                } catch {
                    log.telemetry.error(error)
                    let failureMessage = L10n.FeatureClipDetail.songDeleteFailed
                    showToast(.warning(failureMessage))
                    // FIXME: (JY) - This undo should be more granular. Either we should try to re-fetch the clip, or we need a way to undo this particular thing as a DAG. Probably resolved by ClipClient
                }
            }
        },
        reportInappropriate: { clip in
            Task {
                @Dependency(\.apiClient) var apiClient
                // Toast on both error and success
                @Dependency(\.toastClient.show) var showToast
                do {
                    try await apiClient.updateFlag(clip, true, "flag_inappropriate")
                    showToast(.success(L10n.FeatureClipDetail.songReported))
                } catch {
                    log.telemetry.error(error)
                    showToast(.warning(L10n.FeatureCatalog.actionFailed))
                }
            }
        },
        removeClipFromPlaylist: { clip, playlist in
            Task {
                @Dependency(\.apiClientV2) var apiClientV2
                // Toast on both error and success
                @Dependency(\.toastClient.show) var showToast
                @Dependency(\.eventBus.sendClipEvent) var sendClipEvent

                do {
                    try await apiClientV2.updatePlaylistClips(clip.id, playlist.id, true, nil)
                    sendClipEvent(.removeClipFromPlaylist(clip, playlist: playlist))
                    let successToast = ToastReducer.State.ToastType.success(
                        L10n.FeatureClipList.removedFromPlaylist(playlist.name),
                        position: .bottom
                    )
                    showToast(successToast)
                } catch {
                    log.telemetry.error(error)
                    showToast(.warning(L10n.FeatureCatalog.actionFailed))
                }
            }
        },
        toggleVideoCoverInHooksFeed: { clip, showVideoCover in
            Task {
                let oldClip = clip
                var updatedClip = clip
                updatedClip.optOutVideoCoverHook = !showVideoCover

                @Dependency(\.eventBus.sendClipEvent) var sendClipEvent
                sendClipEvent(.updateClip(updatedClip))

                @Dependency(\.apiClientV2) var apiClientV2
                @Dependency(\.toastClient.show) var showToast

                do {
                    let updatedMetadata = ClipMetadataSpec(optOutVideoCoverHook: !showVideoCover)
                    _ = try await apiClientV2.updateClip(clip.id.remoteId, updatedMetadata)
                    sendClipEvent(.updateClip(updatedClip))
                } catch {
                    log.telemetry.error(error)
                    showToast(.warning(L10n.FeatureClipDetail.actionFailed))
                    sendClipEvent(.updateClip(oldClip))
                }
            }
        },
        goToProfile: { handle in
            @Dependency(NavigationRouterClient.self) var navigationRouter
            navigationRouter.send(route: .profile(handle))
        }
    )
}
