//
//  LivePushReadinessViewModel.swift
//  suixinkan
//
//  Created by Codex on 2026/6/25.
//

import AVFoundation
import Foundation
import Network
import Combine

/// 直播推流权限状态。
enum LivePushPermissionState: Equatable {
    case unknown
    case granted
    case denied

    var displayText: String {
        switch self {
        case .unknown: "未检查"
        case .granted: "已授权"
        case .denied: "未授权"
        }
    }
}

/// 直播推流网络状态。
enum LivePushNetworkState: Equatable {
    case unknown
    case unavailable
    case wifi
    case cellular
    case other

    var displayText: String {
        switch self {
        case .unknown: "检测中"
        case .unavailable: "网络不可用"
        case .wifi: "Wi-Fi"
        case .cellular: "蜂窝网络"
        case .other: "其他网络"
        }
    }
}

/// 推流 SDK 适配协议。后续接入真实 RTMP/RTC SDK 时替换默认实现。
protocol LivePushAdapter {
    var name: String { get }
    var isAvailable: Bool { get }
    func prepare(pushURL: URL) async throws
    func start() async throws
    func stop() async throws
    func dispose() async
}

/// 权限提供者协议，隔离 AVFoundation 便于测试。
protocol LivePermissionProviding {
    func cameraPermission() async -> LivePushPermissionState
    func microphonePermission() async -> LivePushPermissionState
}

/// 网络监听协议，隔离 NWPathMonitor 便于测试。
protocol LiveNetworkMonitoring: AnyObject {
    var currentState: LivePushNetworkState { get }
    func start(_ onChange: @escaping @Sendable (LivePushNetworkState) -> Void)
    func stop()
}

enum LivePushReadinessError: LocalizedError, Equatable {
    case missingPushURL
    case invalidPushURL
    case permissionDenied
    case networkUnavailable
    case sdkUnavailable

    var errorDescription: String? {
        switch self {
        case .missingPushURL:
            "暂无推流地址"
        case .invalidPushURL:
            "推流地址格式无效"
        case .permissionDenied:
            "请先开启相机和麦克风权限"
        case .networkUnavailable:
            "当前网络不可用"
        case .sdkUnavailable:
            "当前版本未接入真推流 SDK"
        }
    }
}

struct UnsupportedLivePushAdapter: LivePushAdapter {
    let name = "未接入推流 SDK"
    let isAvailable = false

    func prepare(pushURL: URL) async throws {
        throw LivePushReadinessError.sdkUnavailable
    }

    func start() async throws {
        throw LivePushReadinessError.sdkUnavailable
    }

    func stop() async throws {}

    func dispose() async {}
}

struct SystemLivePermissionProvider: LivePermissionProviding {
    func cameraPermission() async -> LivePushPermissionState {
        await permission(for: .video)
    }

    func microphonePermission() async -> LivePushPermissionState {
        await permission(for: .audio)
    }

    private func permission(for mediaType: AVMediaType) async -> LivePushPermissionState {
        switch AVCaptureDevice.authorizationStatus(for: mediaType) {
        case .authorized:
            return .granted
        case .notDetermined:
            let granted = await AVCaptureDevice.requestAccess(for: mediaType)
            return granted ? .granted : .denied
        case .denied, .restricted:
            return .denied
        @unknown default:
            return .denied
        }
    }
}

final class SystemLiveNetworkMonitor: LiveNetworkMonitoring {
    private let monitor = NWPathMonitor()
    private let queue = DispatchQueue(label: "com.suixinkan.live.network")
    private(set) var currentState: LivePushNetworkState = .unknown

    func start(_ onChange: @escaping @Sendable (LivePushNetworkState) -> Void) {
        monitor.pathUpdateHandler = { [weak self] path in
            let state = Self.state(from: path)
            self?.currentState = state
            onChange(state)
        }
        monitor.start(queue: queue)
    }

    func stop() {
        monitor.cancel()
    }

    private static func state(from path: NWPath) -> LivePushNetworkState {
        guard path.status == .satisfied else { return .unavailable }
        if path.usesInterfaceType(.wifi) { return .wifi }
        if path.usesInterfaceType(.cellular) { return .cellular }
        return .other
    }
}

@MainActor
/// 推流准备 ViewModel，检查权限、网络和默认 SDK 可用性。
final class LivePushReadinessViewModel: ObservableObject {
    @Published var cameraPermission: LivePushPermissionState = .unknown
    @Published var microphonePermission: LivePushPermissionState = .unknown
    @Published var networkState: LivePushNetworkState = .unknown
    @Published var prepared = false
    @Published var running = false
    @Published var errorMessage: String?

    let adapterName: String
    let sdkStatusText: String

    private let permissionProvider: any LivePermissionProviding
    private let networkMonitor: any LiveNetworkMonitoring
    private let adapter: any LivePushAdapter
    @Published private var pushURL: URL?

    init(
        permissionProvider: any LivePermissionProviding = SystemLivePermissionProvider(),
        networkMonitor: any LiveNetworkMonitoring = SystemLiveNetworkMonitor(),
        adapter: any LivePushAdapter = UnsupportedLivePushAdapter()
    ) {
        self.permissionProvider = permissionProvider
        self.networkMonitor = networkMonitor
        self.adapter = adapter
        self.adapterName = adapter.name
        self.sdkStatusText = adapter.isAvailable ? "已接入" : "未接入真推流 SDK"
        self.networkState = networkMonitor.currentState
    }

    func configure(pushURL rawValue: String) {
        let value = rawValue.liveTrimmed
        guard !value.isEmpty else {
            pushURL = nil
            errorMessage = LivePushReadinessError.missingPushURL.localizedDescription
            return
        }
        guard let url = URL(string: value), url.scheme?.isEmpty == false else {
            pushURL = nil
            errorMessage = LivePushReadinessError.invalidPushURL.localizedDescription
            return
        }
        pushURL = url
        errorMessage = nil
    }

    func startMonitoring() {
        networkState = networkMonitor.currentState
        networkMonitor.start { [weak self] state in
            Task { @MainActor in
                self?.networkState = state
            }
        }
    }

    func stopMonitoring() {
        networkMonitor.stop()
    }

    func refreshPermissions() async {
        async let camera = permissionProvider.cameraPermission()
        async let microphone = permissionProvider.microphonePermission()
        cameraPermission = await camera
        microphonePermission = await microphone
    }

    func runDiagnostics() throws {
        do {
            try validateReadiness()
        } catch {
            errorMessage = error.localizedDescription
            throw error
        }
        guard adapter.isAvailable else {
            errorMessage = LivePushReadinessError.sdkUnavailable.localizedDescription
            throw LivePushReadinessError.sdkUnavailable
        }
        errorMessage = nil
    }

    func prepare() async throws {
        try validateReadiness()
        guard let pushURL else { throw LivePushReadinessError.missingPushURL }
        do {
            try await adapter.prepare(pushURL: pushURL)
            prepared = true
            errorMessage = nil
        } catch {
            errorMessage = error.localizedDescription
            throw error
        }
    }

    func startPush() async throws {
        try validateReadiness()
        guard adapter.isAvailable else {
            errorMessage = LivePushReadinessError.sdkUnavailable.localizedDescription
            throw LivePushReadinessError.sdkUnavailable
        }
        try await adapter.start()
        running = true
        prepared = true
        errorMessage = nil
    }

    func stopPush() async {
        try? await adapter.stop()
        running = false
    }

    func dispose() async {
        stopMonitoring()
        await adapter.dispose()
        running = false
        prepared = false
    }

    private func validateReadiness() throws {
        guard pushURL != nil else {
            throw LivePushReadinessError.missingPushURL
        }
        guard cameraPermission != .denied, microphonePermission != .denied else {
            throw LivePushReadinessError.permissionDenied
        }
        guard networkState != .unavailable else {
            throw LivePushReadinessError.networkUnavailable
        }
    }
}
