import APIClient
import ComposableArchitecture
import Foundation
import GenAPI
import Utilities

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

public enum HooksVideoUploadError: LocalizedError {
    case invalidVideoURL
    case networkAccessLimited
    case streamEndedWithoutCompletion

    public var errorDescription: String? {
        switch self {
        case .invalidVideoURL:
            return "Invalid video URL provided"
        case .networkAccessLimited:
            return "Network access is limited"
        case .streamEndedWithoutCompletion:
            return "Upload stream ended without completion or failure event"
        }
    }
}

@DependencyClient
public struct HooksVideoUploadClient {
    public enum VideoUploadEvent {
        case startedVideoUpload(_ uploadId: String)
        case uploadProgress(_ uploadId: String, _ progress: Double)
        case videoUploadComplete(_ uploadId: String, _ s3Id: String)
        case uploadFailed(_ error: Error)
    }

    public var uploadAndProcessVideo: (
        _ videoURL: URL
    ) -> AsyncStream<VideoUploadEvent> = { _ in .never }

    public var cancelCurrentUpload: () -> Void = {}
}

// Internal actor to manage upload cancellation
private actor UploadCancellationManager {
    private var currentCancelFunction: (() -> Void)?

    func setCancelFunction(_ cancel: (() -> Void)?) {
        currentCancelFunction = cancel
    }

    func cancelCurrent() {
        currentCancelFunction?()
    }

    func clear() {
        currentCancelFunction = nil
    }
}

extension HooksVideoUploadClient: DependencyKey {
    public static var liveValue: HooksVideoUploadClient {
        @Dependency(APIClientV2.self) var api

        let cancellationManager = UploadCancellationManager()

        return Self(
            uploadAndProcessVideo: { videoURL in
                AsyncStream { continuation in
                    let uploadTask = Task {
                        let startTime = Date()

                        do {
                            // Check for cancellation before starting
                            try Task.checkCancellation()

                            // Step 1: Get upload parameters
                            let uploadRequest = try await api.getVideoUploadParams()

                            let uploadId = uploadRequest.id

                            // Check for cancellation after getting upload parameters
                            try Task.checkCancellation()

                            continuation.yield(.startedVideoUpload(uploadId))

                            // Step 2: Upload video to S3 using APIClientV2
                            let uploadSession = api.uploadVideo(videoURL, uploadRequest, false)

                            // Store the cancel function for synchronous cancellation
                            await cancellationManager.setCancelFunction(uploadSession.cancel)

                            // Handle stream termination
                            continuation.onTermination = { _ in
                                uploadSession.cancel()
                                Task {
                                    await cancellationManager.clear()
                                }
                            }

                            // Track completion to detect incomplete streams
                            var didComplete = false
                            var lastProgress: Double = 0.0

                            // Process upload events
                            for await event in uploadSession.stream {
                                // Check for cancellation before processing each event
                                try Task.checkCancellation()

                                switch event {
                                case .updateProgress(let progress):
                                    lastProgress = progress
                                    continuation.yield(.uploadProgress(uploadId, progress))

                                case .success:
                                    // Check for cancellation before finishing upload
                                    try Task.checkCancellation()

                                    // Video uploaded to S3, now finish upload
                                    let fileName = "\(uploadId).mp4"
                                    let finishUploadSpec = FinishUploadSpec(
                                        isVideoCover: false,
                                        uploadFilename: fileName,
                                        uploadType: "file_upload",
                                        videoUploadType: .videoHook
                                    )

                                    do {
                                        try await api.markVideoUploadComplete(uploadId, finishUploadSpec)
                                    } catch {
                                        log.telemetry.error(error, message: "HooksVideoUploadClient: markVideoUploadComplete failed - uploadId: \(uploadId).")
                                        throw error
                                    }

                                    // Construct s3Id directly without waiting for processing
                                    let s3Id = "video_upload_\(uploadId)"
                                    continuation.yield(.videoUploadComplete(uploadId, s3Id))

                                    // Mark as completed
                                    didComplete = true

                                    // Clear the cancel function after successful completion
                                    await cancellationManager.clear()

                                case .postponed:
                                    didComplete = true
                                    log.telemetry.assertionFailure("HooksVideoUploadClient: uploadVideo failed - uploadId: \(uploadId), error: Network access limited")
                                    throw HooksVideoUploadError.networkAccessLimited

                                case .failure(let errorMessage):
                                    didComplete = true
                                    let error = NSError(domain: "HooksVideoUploadClient", code: -1, userInfo: [NSLocalizedDescriptionKey: errorMessage])
                                    log.telemetry.error(error, message: "HooksVideoUploadClient: uploadVideo failed from APIClientV2 - uploadId: \(uploadId)")
                                    await cancellationManager.clear()
                                    continuation.yield(.uploadFailed(error))
                                }
                            }

                            // Detect incomplete streams - if stream ended without terminal event
                            if !didComplete {
                                let error = HooksVideoUploadError.streamEndedWithoutCompletion
                                log.telemetry.error(error, message: "HooksVideoUploadClient: stream ended without completion - uploadId: \(uploadId ?? "none"), lastProgress: \(Int(lastProgress * 100))%")
                                continuation.yield(.uploadFailed(error))
                            }

                        } catch is CancellationError {
                            // Don't report cancellation as failure - just silently finish
                            await cancellationManager.clear()
                        } catch {
                            let duration = Date().timeIntervalSince(startTime)
                            log.telemetry.error(error, message: "HooksVideoUploadClient: uploadVideo failed.")

                            await cancellationManager.clear()
                            continuation.yield(.uploadFailed(error))
                        }
                        continuation.finish()
                    }

                    continuation.onTermination = { _ in
                        uploadTask.cancel()
                    }
                }
            },

            cancelCurrentUpload: {
                Task {
                    await cancellationManager.cancelCurrent()
                }
            }
        )
    }
}

public extension DependencyValues {
    var hooksVideoUploadClient: HooksVideoUploadClient {
        get { self[HooksVideoUploadClient.self] }
        set { self[HooksVideoUploadClient.self] = newValue }
    }
}
