import Foundation
import Observation

enum ONUDetailTab: String, CaseIterable, Identifiable {
    case stats = "Overview"
    case cpe = "CPE"
    case config = "Config"
    case alarms = "Alarms"
    case logs = "Logs"
    var id: String { rawValue }
}

@MainActor
@Observable
final class ONUDetailViewModel {
    let onuId: String
    private let connection: MCMSConnection

    var tab: ONUDetailTab = .stats
    var phase: LoadPhase = .idle

    var config: ONUConfigDoc?
    var liveState: ONUStateDoc?
    var stats: [StatSample] = []
    var alarms: [AlarmHistoryDoc] = []
    var logs: [LogEntry] = []
    var cpes: [CPEState] = []

    var firmwareFiles: [FirmwareFile] = []
    var firmwareLoadError: String?

    var isPerformingAction = false
    var actionMessage: String?

    init(connection: MCMSConnection, onuId: String) {
        self.connection = connection
        self.onuId = onuId
    }

    /// Tab switch / first load: blanks to a spinner while the new tab loads.
    func loadCurrentTab() async {
        phase = .loading
        await reloadCurrentTab()
    }

    /// Pull-to-refresh: reloads in place WITHOUT flipping to `.loading`, so the
    /// scrollable content (and the refresh task driving it) isn't torn down
    /// mid-request — which otherwise surfaces as a spurious "cancelled" error.
    func refresh() async {
        await reloadCurrentTab()
    }

    private func reloadCurrentTab() async {
        do {
            switch tab {
            case .stats:
                // Live ONU-STATE carries the current optical/FEC levels (§3.12);
                // the /onus/stats/ time-series is a best-effort supplement.
                liveState = try await connection.onu.state(id: onuId)
                stats = (try? await connection.onu.stats(id: onuId, lastHours: 24)) ?? []
            case .config: config = try await connection.onu.config(id: onuId)
            case .cpe:    cpes   = try await connection.onu.cpes(id: onuId)
            case .alarms: alarms = try await connection.onu.alarmHistories(
                                query: queryForONU()).sorted { $0.severity.rank < $1.severity.rank }
            case .logs:   logs   = try await connection.onu.logs(id: onuId, lastHours: 24)
            }
            phase = .loaded
        } catch {
            phase = .failed((error as? APIError)?.localizedDescription ?? error.localizedDescription)
        }
    }

    /// Filter alarm-history list to this ONU. Reconcile the key with the real
    /// document shape; `_id` is a safe default for the per-ONU collection.
    private func queryForONU() -> APIQuery {
        var q = APIQuery()
        q.query = ["_id": onuId]
        return q
    }

    func reset() async {
        isPerformingAction = true
        actionMessage = nil
        do {
            try await connection.onu.reset(id: onuId)
            actionMessage = "Reset requested."
        } catch {
            actionMessage = (error as? APIError)?.localizedDescription ?? error.localizedDescription
        }
        isPerformingAction = false
    }

    func loadFirmware() async {
        firmwareLoadError = nil
        do { firmwareFiles = try await connection.onu.firmwareFiles() }
        catch { firmwareLoadError = (error as? APIError)?.localizedDescription ?? error.localizedDescription }
    }

    /// Stage a firmware image to the ONU's inactive bank (Procedure 7). Service-affecting.
    func upgrade(to file: FirmwareFile) async {
        isPerformingAction = true
        actionMessage = nil
        do {
            let slot = try await connection.onu.upgradeFirmware(id: onuId, file: file)
            actionMessage = "Firmware \(file.version ?? "image") staged to bank \(slot). The ONU starts it after its next reboot."
        } catch {
            actionMessage = (error as? APIError)?.localizedDescription ?? error.localizedDescription
        }
        isPerformingAction = false
    }
}

extension StatSample {
    /// All numeric counters in the sample, sorted by key — for display/charting.
    var numericMetrics: [(key: String, value: Double)] {
        (raw.objectValue ?? [:]).compactMap { key, value in
            switch value {
            case .double(let d): return (key, d)
            case .int(let i): return (key, Double(i))
            default: return nil
            }
        }
        .sorted { $0.key < $1.key }
    }
}
