import AnalyticsClient
import APIClient
import AsyncAlgorithms
import Combine
import ComposableArchitecture
import Foundation
import Localization
import Utilities
import Photos

@DependencyClient
public struct ShareAssetClient {
    public enum ShareAssetClientEvent {
        case startedRenderingShareAsset(_ clip: Clip, _ assetID: String)
        case polledRendering(_ clip: Clip, _ assetID: String, _ target: ShareAssetCreationTarget, _ pollingCount: Int)
        case shareAssetReady(
            _ clip: Clip,
            _ assetID: String,
            _ target: ShareAssetCreationTarget,
            _ sourceURL: URL,
            _ localCacheURL: URL,
            _ attributionURL: URL,
            _ triggerID: String
        )
        case shareAssetCouldNotRender(_ clip: Clip, _ triggerID: String)
    }

    public var stream: () -> AsyncStream<ShareAssetClientEvent> = { .never }
    public var configure: () async -> Void = {}
    public var getSavedShareAssetGenerationForClip: (_ clip: ClipID) -> URL?

    public var isCreationMuted: () -> Bool = { false }
    public var setCreationMuted: (Bool) -> Void = { _ in }

    // Responses for requests are triggered through AsyncChannel
    public var triggerCreateShareAsset: (
        _ clip: Clip,
        _ triggerID: String, /* To stop race condition if you are on same song share asset page and catch previous event */
        _ config: ShareAssetConfig,
        _ startTime: TimeInterval,
        _ endTime: TimeInterval,
        _ attributionURL: URL,
        _ assetTarget: ShareAssetCreationTarget
    ) -> Void = { _, _, _, _, _, _, _ in }

    public var shareToDestination: (
        _ shareURL: URL,
        _ videoURL: URL,
        _ pngStickerData: Data,
        _ linkMessage: String,
        _ shareDestination: ShareAssetCreationTarget
    ) -> Void = { _, _, _, _, _ in }

    public var saveToDownloadPhotos: (_ videoURL: URL) async -> Void = { _ in }
}

extension ShareAssetClient: DependencyKey {
    public static var liveValue: ShareAssetClient {
        @Dependency(APIClientV2.self) var api
        let subject = PassthroughSubject<ShareAssetClientEvent, Never>()

        return Self(
            stream: {
                UncheckedSendable(subject.values).eraseToStream()
            },
            configure: {
                do {
                    let resourceCache = ShareAssetResourceCache()
                    try resourceCache.setupShareAssetResourceCacheIfNeeded()
                    try resourceCache.clearCache(exclude: [])
                } catch {
                    print("ShareAssetClient: Couldn't setup cache")
                }
            },
            getSavedShareAssetGenerationForClip: { clipID in
                let resourceCache = ShareAssetResourceCache()
                return resourceCache.getCachedFile(for: .init(clipID.remoteId, type: .mp4))
            },
            isCreationMuted: {
                @Shared(.appStorage(.isShareAssetCreationMuted)) var isShareAssetCreationMuted: Bool = false
                return isShareAssetCreationMuted
            },
            setCreationMuted: { newValue in
                @Shared(.appStorage(.isShareAssetCreationMuted)) var isShareAssetCreationMuted: Bool = false
                $isShareAssetCreationMuted.withLock { $0 = newValue }
            },
            triggerCreateShareAsset: { clip, triggerID, config, startTime, endTime, attributionURL, shareAssetTarget in
                Task {
                    @Dependency(\.analyticsClient.trackV2) var trackV2

                    do {
                        let startGenerateResponse = try await api.startShareAssetGeneration(clip.id, .init(
                            assetConfig: config.asDataDictionary,
                            clipEndTime: endTime,
                            clipStartTime: startTime
                        ))

                        let assetID = startGenerateResponse.assetId.uuidString
                        subject.send(.startedRenderingShareAsset(clip, assetID))

                        /* Poll the result of asset generation */
                        let manager = ShareAssetManager()
                        manager.addGenerationItem(.init(
                            id: assetID,
                            clip: clip,
                            status: .inProgress,
                            assetConfig: config,
                            startTime: startTime,
                            endTime: endTime
                        ))

                        let pollingInterval: TimeInterval = 5.0
                        let resourceCache = ShareAssetResourceCache()

                        var pollingCount: Int = .zero

                        while manager.shouldContinuePolling(assetID) {
                            subject.send(.polledRendering(clip, assetID, shareAssetTarget, pollingCount))
                            pollingCount += 1
                            let statusResponse = try await api.pollShareAssetGeneration(clip.id, assetID)
                            manager.updateGenerationItem(assetID, statusResponse)
                            let shouldPoll = manager.shouldContinuePolling(assetID)
                            if !shouldPoll, let item = manager.generationItemFor(assetID) {
                                /*
                                    We will save the last clip render only and
                                    let people use their last rendered item
                                 */
                                let clipID = clip.id.remoteId
                                if let sourceURLString = statusResponse.assetUrl,
                                   let sourceURL = URL(string: sourceURLString)
                                {
                                    do {
                                        let localURL = try await resourceCache.downloadAndSetCacheResource(
                                            for: .init(clipID, type: .mp4),
                                            from: sourceURL
                                        )

                                        trackV2(
                                            Event(
                                                category: .share,
                                                actionName: .shareAssetVisualizerCreationSucceeded,
                                                actionType: .event,
                                                elementType: .none,
                                                elementText: "\(clipID):\(startTime.format_m_ss),\(endTime.format_m_ss)",
                                                context: config.compositeID
                                            ),
                                            "share_asset_client",
                                            .standard
                                        )

                                        subject.send(.shareAssetReady(
                                            clip,
                                            item.id,
                                            shareAssetTarget,
                                            sourceURL,
                                            localURL,
                                            attributionURL,
                                            triggerID
                                        ))
                                    } catch {
                                        subject.send(.shareAssetCouldNotRender(clip, triggerID))
                                        log.telemetry.error(error)
                                        trackV2(
                                            Event(
                                                category: .share,
                                                actionName: .shareAssetVisualizerCreationFailed,
                                                actionType: .event,
                                                elementType: .none,
                                                elementText: "\(clipID):\(startTime.format_m_ss),\(endTime.format_m_ss)",
                                                context: config.compositeID
                                            ),
                                            "share_asset_client",
                                            .standard
                                        )
                                    }
                                }
                            } else {
                                try await Task.sleep(for: .seconds(pollingInterval))
                            }
                        }

                    } catch {
                        log.telemetry.error(error)
                        subject.send(.shareAssetCouldNotRender(clip, triggerID))
                    }
                }
            },
            shareToDestination: { shareURL, videoURL, pngStickerData, linkMessage, assetTarget in

                @Dependency(\.analyticsClient.trackV2) var trackV2
                trackV2(
                    Event(
                        category: .share,
                        actionName: .shareSheetTakeUserToDestinationWithAsset,
                        actionType: .event,
                        elementType: .none,
                        context: assetTarget.rawValue
                    ),
                    "share_asset_client",
                    .standard
                )

                Task {
                    var destination: ShareAssetDestination {
                        switch assetTarget {
                        case .downloadVideo:
                            return .download
                        case .facebookStories:
                            return .facebook
                        case .instagramStories:
                            return .instagram
                        case .tiktokVideo:
                            return .tiktok
                        }
                    }

                    do {
                        _ = try await destination.share(.init(
                            shareURL: shareURL,
                            stickerData: pngStickerData, // PNG Data if you want it
                            downloadableURL: videoURL,
                            linkMessage: linkMessage
                        ))

                        /* Save to photo library automatically */
                        do {
                            // 1. Request “add‑only” permissions
                            let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly)
                            guard status == .authorized else {
                                print("Photo Library access not granted")
                                return
                            }

                            // 2. Move the file into the Photos library
                            try await PHPhotoLibrary.shared().performChanges {
                                PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
                            }
                        } catch {
                            print("Error saving video: \(error)")
                        }

                    } catch {
                        // TODO: Handle Error Toasts
                    }
                }
            },
            saveToDownloadPhotos: { url in
                do {
                    // 1. Request “add‑only” permissions
                    let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly)
                    guard status == .authorized else {
                        print("Photo Library access not granted")
                        return
                    }

                    // 2. Move the file into the Photos library
                    try await PHPhotoLibrary.shared().performChanges {
                        PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
                    }
                } catch {
                    print("Error saving video: \(error)")
                }
            }
        )
    }
}

public extension DependencyValues {
    var shareAssetClient: ShareAssetClient {
        get { self[ShareAssetClient.self] }
        set { self[ShareAssetClient.self] = newValue }
    }
}
