import APIClient
import ComposableArchitecture
import Foundation
import Utilities

/// Client for handling audio uploads in the Chat feature
@DependencyClient
public struct ChatAudioClient {
    public var uploadAudio: @Sendable (AudioRecording) -> AsyncThrowingStream<AudioUploadStatus, Error> = { _ in .never }
    public var initializeClip: @Sendable (_ uploadRequestId: String) async throws -> String
}

extension ChatAudioClient {
    public enum AudioUploadStatus: Equatable {
        case uploadEvent(APIClient.UploadEvent)
        case status(UploadRequestStatus)
    }

    public enum AudioUploadError: Error {
        case fileCreationFailed
        case verificationFailed
        case statusCheckFailed
        case timeout
    }

    public enum InitializeClipError: Error {
        case noClipIdReturned
    }
}

extension ChatAudioClient: DependencyKey {
    public static let liveValue: Self = {
        @Dependency(APIClient.self) var apiClient
        @Dependency(\.continuousClock) var clock

        return Self(
            uploadAudio: { audioRecording in
                AsyncThrowingStream<AudioUploadStatus, Error> { continuation in
                    let task = Task {
                        // Ensure temp file cleanup on completion or error
                        defer {
                            try? FileManager.default.removeItem(at: audioRecording.url)
                        }

                        // Step 1: Create upload request
                        let uploadRequest: UploadRequest
                        do {
                            uploadRequest = try await apiClient.createAudioFile()
                        } catch {
                            continuation.finish(throwing: AudioUploadError.fileCreationFailed)
                            return
                        }

                        // Step 2: Upload file with progress
                        for await event in apiClient.uploadStream(input: audioRecording.url, uploadRequest: uploadRequest) {
                            continuation.yield(.uploadEvent(event))

                            switch event {
                            case .updateProgress:
                                continue
                            case .success:
                                break
                            case .failure(let error):
                                continuation.finish(throwing: error)
                                return
                            }
                        }

                        // Step 3: Finish upload
                        do {
                            try await apiClient.finishAudioUpload(uploadRequest.id, audioRecording.title)
                        } catch {
                            continuation.finish(throwing: AudioUploadError.verificationFailed)
                            return
                        }

                        // Step 4: Poll for status with timeout (2 minutes max)
                        enum PollResult {
                            case completed
                            case failed(Error)
                            case timeout
                        }

                        let result = await withTaskGroup(of: PollResult.self) { group in
                            // Polling task
                            group.addTask {
                                do {
                                    for await _ in clock.timer(interval: .seconds(3)) {
                                        let status = try await apiClient.uploadAudioStatus(uploadRequest.id)
                                        continuation.yield(.status(status))

                                        if status.errorMessage != nil || status.status == .complete {
                                            return .completed
                                        }
                                    }
                                    return .completed
                                } catch {
                                    return .failed(error)
                                }
                            }

                            // Timeout task
                            group.addTask {
                                try? await clock.sleep(for: .seconds(120))
                                return .timeout
                            }

                            // Wait for first task to complete, then cancel others
                            let firstResult = await group.next()
                            group.cancelAll()
                            return firstResult ?? .completed
                        }

                        switch result {
                        case .completed:
                            continuation.finish()
                        case .failed:
                            continuation.finish(throwing: AudioUploadError.statusCheckFailed)
                        case .timeout:
                            continuation.finish(throwing: AudioUploadError.timeout)
                        }
                    }

                    continuation.onTermination = { @Sendable _ in
                        task.cancel()
                    }
                }
            },
            initializeClip: { uploadRequestId in
                guard let clipId = try await apiClient.initializeClip(uploadId: uploadRequestId) else {
                    throw InitializeClipError.noClipIdReturned
                }
                return clipId
            }
        )
    }()
}

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