import AVFoundation
import APIClient
import ComposableArchitecture
import Errors
import UserDefaultsClient
import Utilities

@Reducer
public struct ChatAudio {
    private let log = Logger(category: "ChatAudio")
    @Reducer(state: .equatable)
    public enum Destination {
        case documentPicker
        case termsOfService
        case recording(ChatAudioRecorder)
    }

    @ObservableState
    public struct State: Equatable {
        @Presents public var destination: Destination.State?

        // Audio file state (for UI display in ChatBar)
        public var audioFileName: String?
        public var audioFileDuration: TimeInterval?

        // Upload state (managed by parent, but displayed here)
        public var uploadProgress: Double = 0.0
        public var isUploading: Bool = false
        public var uploadCompleted: Bool = false

        // TOS state
        public var pendingActionAfterTOS: PendingAction?

        public enum PendingAction: Equatable {
            case upload
            case record
        }

        // Permission state
        public var showMicrophonePermissionAlert: Bool = false

        public var hasActiveAudio: Bool {
            audioFileName != nil
        }

        public init() {}
    }

    public enum Action: BindableAction {
        public enum Delegate: Equatable {
            case uploadCompleted(String) // clipId - parent needs this to include in message
        }

        case task
        case binding(BindingAction<State>)
        case delegate(Delegate)
        case destination(PresentationAction<Destination.Action>)

        // User actions
        case uploadTapped
        case recordTapped
        case checkMicrophonePermissionResponse(Bool)
        case closeAudioTapped

        // File processing
        case processAudioFile(Result<URL, AnyError>)
        case processAudioFileDidFinish(URL, String, Result<TimeInterval, AnyError>) // Using TimeInterval instead of CMTime for Equatable

        // Upload actions
        case startUpload(AudioRecording)
        case uploadEvent(ChatAudioClient.AudioUploadStatus)
        case uploadCompleted(clipId: String) // Internal: sends delegate after clip initialization
        case uploadError(Error)
        case resetUploadCompleted // Internal: reset completed state after showing green

        // TOS actions
        case termsOfServiceAccepted
        case termsOfServiceDismissed

        // Permission actions
        case microphonePermissionAlertDismissed
    }

    @Dependency(\.date) private var date
    @Dependency(\.temporaryDirectory) private var temporaryDirectory
    @Dependency(\.uuid) private var uuid
    @Dependency(UserDefaultsClient.self) private var userDefaults
    @Dependency(ChatAudioClient.self) private var chatAudioClient
    @Dependency(\.continuousClock) private var clock

    static let audioTitleFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "MM-dd-yyyy_HH:mm:ss"
        return formatter
    }()


    private var newRecordingAudio: ChatAudioRecorder.State {
        ChatAudioRecorder.State(
            date: date.now,
            url: temporaryDirectory()
                .appendingPathComponent(uuid().uuidString)
                .appendingPathExtension("m4a")
        )
    }

    public init() {}

    @Dependency(\.audioRecorder) private var audioRecorder

    public var body: some ReducerOf<Self> {
        BindingReducer()
        Reduce<State, Action> { state, action in
            switch action {
            case .task:
                return .run { _ in
                    // Pause any playing audio when starting
                    @Dependency(\.omniplayerClient.pauseCurrentClip) var pauseCurrentClip
                    pauseCurrentClip()
                }

            case .uploadTapped:
                guard userDefaults.hasAcceptedAudioUploadTOS else {
                    state.pendingActionAfterTOS = .upload
                    state.destination = .termsOfService
                    return .none
                }
                state.destination = .documentPicker
                return .none

            case .recordTapped:
                guard userDefaults.hasAcceptedAudioUploadTOS else {
                    state.pendingActionAfterTOS = .record
                    state.destination = .termsOfService
                    return .none
                }
                return .run { send in
                    let granted = await AVAudioApplication.requestRecordPermission()
                    await send(.checkMicrophonePermissionResponse(granted))
                }

            case .checkMicrophonePermissionResponse(let granted):
                if granted {
                    state.destination = .recording(newRecordingAudio)
                } else {
                    state.showMicrophonePermissionAlert = true
                }
                return .none

            case .closeAudioTapped:
                state.audioFileName = nil
                state.audioFileDuration = nil
                state.uploadProgress = 0.0
                state.isUploading = false
                state.uploadCompleted = false
                state.destination = nil
                return .none

            case .processAudioFile(.success(let originalUrl)):
                let tempUrl = temporaryDirectory()
                    .appendingPathComponent(uuid().uuidString)
                    .appendingPathExtension(originalUrl.pathExtension)
                let fileName = originalUrl.deletingPathExtension().lastPathComponent

                return .run { send in
                    do {
                        // Create a copy from original URL instead of passing directly
                        try FileManager.default.copyItem(at: originalUrl, to: tempUrl)

                        // Load audio metadata
                        let asset = AVURLAsset(url: tempUrl)
                        let duration = try await asset.load(.duration)
                        await send(.processAudioFileDidFinish(tempUrl, fileName, .success(duration.seconds)))
                    } catch {
                        try? FileManager.default.removeItem(at: tempUrl)
                        await send(.processAudioFileDidFinish(tempUrl, fileName, .failure(AnyError(error))))
                    }
                }

            case .processAudioFile(.failure(let error)):
                // TODO: Add error state
                return .none

            case .processAudioFileDidFinish(let tempUrl, let fileName, .success(let duration)):
                state.audioFileName = fileName.isEmpty ? "Recording" : fileName
                state.audioFileDuration = duration
                state.destination = nil

                let audioRecording = AudioRecording(
                    date: date.now,
                    duration: duration,
                    title: fileName.isEmpty ? Self.audioTitleFormatter.string(from: date.now) : fileName,
                    url: tempUrl
                )
                // Start upload immediately after file is processed
                return .send(.startUpload(audioRecording))

            case .processAudioFileDidFinish(_, _, .failure(let error)):
                // Error handling
                return .none

            case .termsOfServiceAccepted:
                state.destination = nil
                let pendingAction = state.pendingActionAfterTOS
                state.pendingActionAfterTOS = nil
                return .run { send in
                    await userDefaults.setHasAcceptedAudioUploadTOS(true)
                    // Re-trigger the original action after accepting TOS
                    switch pendingAction {
                    case .upload:
                        await send(.uploadTapped)
                    case .record:
                        await send(.recordTapped)
                    case .none:
                        break
                    }
                }

            case .termsOfServiceDismissed:
                state.destination = nil
                state.pendingActionAfterTOS = nil
                return .none

            case .microphonePermissionAlertDismissed:
                state.showMicrophonePermissionAlert = false
                return .none

            case .destination(.presented(.recording(.delegate(.didFinish(.success(let audioRecording)))))):
                state.destination = nil
                state.audioFileName = audioRecording.title
                state.audioFileDuration = audioRecording.duration
                return .send(.startUpload(audioRecording))

            case .destination(.presented(.recording(.delegate(.didFinish(.failure(let error)))))):
                state.destination = nil
                log.telemetry.error(error)
                // TODO: Show error toast
                return .none

            case .destination(.dismiss):
                if case .recording(let recordingState) = state.destination {
                    let recordingUrl = recordingState.url
                    let trimmedUrl = recordingState.trimmedUrl
                    let isRecording = recordingState.hasStartedRecording
                    state.destination = nil
                    var urlsToCleanup = [recordingUrl]
                    if let trimmedUrl = trimmedUrl {
                        urlsToCleanup.append(trimmedUrl)
                    }
                    return .run { send in
                        if isRecording {
                            await audioRecorder.stopRecordingWithEngine(shouldDeactivateSession: true)
                        }
                        for url in urlsToCleanup {
                            try? FileManager.default.removeItem(at: url)
                        }
                    }
                }
                state.destination = nil
                return .none

            case .startUpload(let audioRecording):
                state.isUploading = true
                state.uploadProgress = 0.0
                state.uploadCompleted = false // Reset for new upload
                return .run { send in
                    do {
                        for try await event in chatAudioClient.uploadAudio(audioRecording) {
                            await send(.uploadEvent(event))
                        }
                    } catch {
                        await send(.uploadError(error))
                    }
                }

            case .uploadEvent(let status):
                switch status {
                case .uploadEvent(let event):
                    switch event {
                    case .updateProgress(let progress):
                        state.uploadProgress = progress
                        return .none
                    case .success:
                        state.uploadProgress = 1.0
                        state.isUploading = false
                        state.uploadCompleted = true
                        return .merge(
                            // Fade away progress bar after showing green for 1 second
                            .run { send in
                                try await clock.sleep(for: .seconds(1))
                                await send(.resetUploadCompleted)
                            }
                        )
                    case .failure(let error):
                        return .send(.uploadError(error))
                    }
                case .status(let uploadStatus):
                    if let errorMessage = uploadStatus.errorMessage {
                        return .send(.uploadError(NSError(domain: "ChatAudio", code: -1, userInfo: [NSLocalizedDescriptionKey: errorMessage])))
                    }

                    switch uploadStatus.status {
                    case .complete:
                        return .run { send in
                            do {
                                let clipId = try await chatAudioClient.initializeClip(uploadRequestId: uploadStatus.id)
                                await send(.uploadCompleted(clipId: clipId))
                            } catch {
                                await send(.uploadError(error))
                            }
                        }
                    default:
                        // Continue polling
                        return .none
                    }
                }

            case .uploadCompleted(let clipId):
                return .send(.delegate(.uploadCompleted(clipId)))

            case .uploadError(let error):
                state.isUploading = false
                // TODO: Add error state
                return .none

            case .resetUploadCompleted:
                state.uploadCompleted = false
                return .none

            case .destination, .delegate, .binding:
                return .none
            }
        }
        .ifLet(\.$destination, action: \.destination)
    }
}

extension ChatAudio.Action: Equatable {
    public static func == (lhs: Self, rhs: Self) -> Bool {
        switch (lhs, rhs) {
        case (.task, .task),
             (.uploadTapped, .uploadTapped),
             (.recordTapped, .recordTapped),
             (.closeAudioTapped, .closeAudioTapped):
            return true
        case let (.binding(lhs), .binding(rhs)):
            return lhs == rhs
        case let (.delegate(lhs), .delegate(rhs)):
            return lhs == rhs
        case let (.processAudioFile(lhs), .processAudioFile(rhs)):
            switch (lhs, rhs) {
            case let (.success(lhsUrl), .success(rhsUrl)):
                return lhsUrl == rhsUrl
            case let (.failure(lhsError), .failure(rhsError)):
                return lhsError == rhsError
            default:
                return false
            }
        case let (.processAudioFileDidFinish(lhsUrl, lhsName, lhsResult), .processAudioFileDidFinish(rhsUrl, rhsName, rhsResult)):
            guard lhsUrl == rhsUrl && lhsName == rhsName else { return false }
            switch (lhsResult, rhsResult) {
            case let (.success(lhsDuration), .success(rhsDuration)):
                return lhsDuration == rhsDuration
            case let (.failure(lhsError), .failure(rhsError)):
                return lhsError == rhsError
            default:
                return false
            }
        case let (.uploadCompleted(lhs), .uploadCompleted(rhs)):
            return lhs == rhs
        case (.resetUploadCompleted, .resetUploadCompleted):
            return true
        case let (.startUpload(lhs), .startUpload(rhs)):
            return lhs == rhs
        case let (.uploadEvent(lhs), .uploadEvent(rhs)):
            switch (lhs, rhs) {
            case let (.uploadEvent(lhsEvent), .uploadEvent(rhsEvent)):
                switch (lhsEvent, rhsEvent) {
                case let (.updateProgress(lhsProgress), .updateProgress(rhsProgress)):
                    return lhsProgress == rhsProgress
                case (.success, .success):
                    return true
                case let (.failure(lhsError), .failure(rhsError)):
                    return lhsError.localizedDescription == rhsError.localizedDescription
                default:
                    return false
                }
            case let (.status(lhsStatus), .status(rhsStatus)):
                return lhsStatus == rhsStatus
            default:
                return false
            }
        case let (.uploadError(lhs), .uploadError(rhs)):
            return lhs.localizedDescription == rhs.localizedDescription
        case (.termsOfServiceAccepted, .termsOfServiceAccepted),
             (.termsOfServiceDismissed, .termsOfServiceDismissed):
            return true
         case let (.destination(lhs), .destination(rhs)):
             // PresentationAction comparison - just compare the cases, not the inner actions
             // This is sufficient for Equatable conformance since PresentationAction
             // is mainly used for navigation state, not deep value comparison
             switch (lhs, rhs) {
             case (.presented, .presented):
                 return true
             case (.dismiss, .dismiss):
                 return true
             default:
                 return false
             }
        default:
            return false
        }
    }
}
