import APIClient
import ComposableArchitecture
import EventBusClient
import FeatureToasts
import Foundation
import HooksPlayerClient
import Localization
import Utilities

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

// MARK: - HookActionsClient

public struct HookActionsClient {
    public var reportInappropriate: @Sendable (_ hook: Hook, _ type: String) -> Void = { _, _ in }
    public var showReportedHook: @Sendable (_ hookId: String) -> Void = { _ in }
    public var deleteHook: @Sendable (_ hook: Hook) -> Void = { _ in }
    public var hideCreator: @Sendable (_ hook: Hook, _ source: HooksFeedSource?) -> Void = { _, _ in }
    public var toggleDislike: @Sendable (_ hookId: String, _ recommendationItemId: String?) -> Void = { _, _ in }
    public var toggleComments: @Sendable (_ hookId: Hook.ID, _ canComment: Bool) -> Void = { _, _ in }
    public var togglePublishClip: @Sendable (_ hook: Hook) -> Void = { _ in }
    public var confirmPublishClip: @Sendable (_ hook: Hook) -> Void = { _ in }
}

// MARK: - Dependency

public extension DependencyValues {
    var hookActionsClient: HookActionsClient {
        get { self[HookActionsClient.self] }
        set { self[HookActionsClient.self] = newValue }
    }
}

// MARK: - Implementation

extension HookActionsClient: DependencyKey {
    public static let liveValue = Self.makeLiveValue()

    private static func makeLiveValue() -> Self {
        return Self(
            reportInappropriate: { hook, type in
                Task {
                    @Dependency(\.apiClientV2) var apiClient
                    @Dependency(\.toastClient) var toastClient
                    @Dependency(\.eventBus.sendHookEvent) var sendHookEvent

                    do {
                        _ = try await apiClient.reportHook(hook.id, type, hook.recommendationItemId)
                        sendHookEvent(.hookReported(hookId: hook.id))
                    } catch {
                        toastClient.show(.warning(L10n.FeatureHooks.actionNotComplete))
                    }
                }
            },
            showReportedHook: { hookId in
                Task {
                    @Dependency(\.eventBus.sendHookEvent) var sendHookEvent

                    // Simply send the event to enable playback for this reported hook
                    // No API call needed as this is only a local state change
                    sendHookEvent(.showReportedHook(hookId: hookId))
                }
            },
            deleteHook: { hook in
                Task {
                    @Dependency(\.apiClientV2) var apiClient
                    @Dependency(\.toastClient) var toastClient
                    @Dependency(\.eventBus.sendHookEvent) var sendHookEvent

                    do {
                        _ = try await apiClient.deleteHook(hook.id)
                        toastClient.show(.success(L10n.FeatureHooks.hookDeleted))
                        sendHookEvent(.hookDeleted(hookId: hook.id))
                    } catch {
                        toastClient.show(.warning(L10n.FeatureHooks.couldNotDelete))
                    }
                }
            },
            hideCreator: { hook, source in
                Task {
                    @Dependency(\.apiClientV2) var apiClient
                    @Dependency(\.toastClient) var toastClient
                    @Dependency(\.eventBus.sendHookEvent) var sendHookEvent

                    do {
                        let recommendationMetadata = HooksRecommendationMetadata(contextType: source?.analyticsContext.contextType, hookId: hook.id, recommendationItemId: hook.recommendationItemId)
                        _ = try await apiClient.toggleHideCreator(HideCreatorContentType.hook, hook.authorUserHandle, recommendationMetadata, false)
                        sendHookEvent(.creatorHidden(hookId: hook.id, handle: hook.authorUserHandle))
                    } catch {
                        toastClient.show(.warning(L10n.FeatureCatalog.actionFailed))
                    }
                }
            },
            toggleDislike: { hookId, recommendationItemId in
                Task {
                    @Dependency(\.apiClientV2) var apiClient
                    @Dependency(\.eventBus.sendHookEvent) var sendHookEvent
                    @Dependency(\.hooksPlayerClient) var hooksPlayerClient
                    @Dependency(\.toastClient.show) var showToast

                    // Get current status from cache
                    let currentStatus = await hooksPlayerClient.getHookDislikeStatus(hookId) ?? false
                    let isDisliked = !currentStatus
                    let action: HookReaction = isDisliked ? .dislike : .undislike

                    // Update cache optimistically
                    await hooksPlayerClient.setHookDislikeStatus(isDisliked, hookId)

                    // Send event optimistically
                    if isDisliked {
                        sendHookEvent(.hookDisliked(hookId: hookId))
                    } else {
                        sendHookEvent(.hookUndisliked(hookId: hookId))
                    }

                    do {
                        let recommendationMetadata = HooksRecommendationMetadata(
                            recommendationItemId: recommendationItemId
                        )
                        try await apiClient.setHookReaction(hookId, action, recommendationMetadata)

                        // Show success toast
                        let successMessage = isDisliked ? L10n.FeatureHooks.hookDisliked : L10n.FeatureHooks.removeDislike
                        showToast(.success(successMessage))
                    } catch {
                        log.telemetry.error(error, message: "Failed to toggle dislike for \(hookId).")

                        // Revert optimistic update
                        await hooksPlayerClient.setHookDislikeStatus(currentStatus, hookId)

                        // Revert event
                        if currentStatus {
                            sendHookEvent(.hookDisliked(hookId: hookId))
                        } else {
                            sendHookEvent(.hookUndisliked(hookId: hookId))
                        }

                        // Show error toast
                        let errorMessage = L10n.FeatureCatalog.actionFailed
                        showToast(.warning(errorMessage))
                    }
                }
            },
            toggleComments: { hookId, canComment in
                Task {
                    @Dependency(\.apiClientV2) var apiClient
                    @Dependency(\.toastClient.show) var showToast
                    @Dependency(\.eventBus.sendHookEvent) var sendHookEvent

                    do {
                        let res = try await apiClient.toggleHookComments(hookId, canComment)
                        sendHookEvent(.hookCommentsToggled(hookId: hookId, canComment: canComment))
                    } catch {
                        log.telemetry.error(error, message: "Failed to toggle comments for \(hookId).")
                        let errorMessage = L10n.FeatureCatalog.actionFailed
                        showToast(.warning(errorMessage))
                    }
                }
            },
            togglePublishClip: { hook in
                Task {
                    @Dependency(\.eventBus.sendHookEvent) var sendHookEvent
                    sendHookEvent(.showPublishSongAlert(hook: hook))
                }
            },
            confirmPublishClip: { hook in
                Task {
                    guard let clip = hook.clip else { return }

                    @Dependency(\.apiClientV2) var apiClient
                    @Dependency(\.toastClient.show) var showToast
                    @Dependency(\.eventBus.sendClipEvent) var sendClipEvent
                    @Dependency(\.eventBus.sendHookEvent) var sendHookEvent

                    // Store old state for potential rollback
                    let oldClip = clip
                    let oldHook = hook

                    // Create updated clip optimistically
                    var updatedClip = clip
                    updatedClip.isPublic = true

                    // Create updated hook with the updated clip
                    var updatedHook = hook
                    updatedHook.clip = updatedClip

                    // Send events optimistically (UI updates instantly)
                    sendClipEvent(.updateClip(updatedClip))
                    sendHookEvent(.hookUpdated(updatedHook))

                    do {
                        try await apiClient.setVisibility(updatedClip, true)
                        showToast(.success(L10n.FeatureHooks.publishSongSuccessMessage, position: .top))
                    } catch {
                        // Revert optimistic updates on failure
                        sendClipEvent(.updateClip(oldClip))
                        sendHookEvent(.hookUpdated(oldHook))
                        showToast(.warning(L10n.FeatureHooks.somethingWentWrong, position: .top))
                        log.telemetry
                            .error(error, message: "Failed to publish clip: \(clip.id.remoteId) for hook: \(hook.id).")
                    }
                }
            }
        )
    }
}

extension HookActionsClient: TestDependencyKey {
    public static let previewValue = Self()
    public static let testValue = Self()
}
