import Foundation
import Observation

@MainActor
@Observable
final class DashboardViewModel {
    var phase: LoadPhase = .idle
    var onuCounts: [(state: ONULifecycleState, count: Int)] = []
    var onuTotal = 0
    var oltCount = 0
    var controllerCount = 0
    var severityCounts: [(severity: AlarmSeverity, count: Int)] = []

    // Aggregates derived from the state docs already fetched for the counts above
    // — no extra API calls.
    var totalDownstreamBps = 0.0
    var totalUpstreamBps = 0.0
    var abnormalRxCount = 0          // ONUs with Rx optical < −28 or > −10 dBm
    var lasersOffCount = 0           // OLTs with the laser shut down
    var opticalAvailable = false     // whether the bulk ONU-STATE response carried Rx levels

    // The actual offenders, kept for drill-down (no re-fetch).
    var abnormalRxONUs: [ONUStateDoc] = []
    var lasersOffOLTs: [OLTStateDoc] = []

    private let connection: MCMSConnection
    /// Bumped per load; only the latest load commits, so the 30s auto-refresh loop
    /// and a manual pull-to-refresh can't interleave partial writes.
    private var loadGeneration = 0
    init(connection: MCMSConnection) { self.connection = connection }

    /// Light by default: the ONU count, state breakdown, OLT traffic and
    /// laser-off count all come from the (small) OLT-STATE docs. The heavy
    /// per-ONU optical scan runs only when `extraStats` is on.
    func load(extraStats: Bool = false) async {
        loadGeneration += 1
        let gen = loadGeneration
        phase = .loading
        do {
            // OLT-STATE drives the OLT count, ONU registration (dev guide
            // Procedure 2), total traffic, and laser state — all from a handful
            // of small docs. Fetch everything into locals first, then commit once,
            // so an overlapping load can't publish a half-updated dashboard.
            let olts = try await connection.olt.allStates()
            let registration = OLTRepository.registrationMap(from: olts)

            // Best-effort sections: nil means "fetch failed this cycle" → keep the
            // last good value rather than flicker to empty/zero on a transient blip.
            let controllers = try? await connection.controller.allConfigs().count
            let alarms = try? await connection.onu.alarmHistories()

            // Opt-in heavy scan: pull every ONU-STATE for the Rx optical check.
            var optAvailable = false
            var abnormal: [ONUStateDoc] = []
            if extraStats {
                let onus = try await connection.onu.allStates()
                optAvailable = onus.contains { $0.rxOpticalDBm != nil }
                abnormal = onus.filter { $0.rxOpticalDBm.map(OpticalThreshold.rxAbnormal) ?? false }
            }

            guard gen == loadGeneration, !Task.isCancelled else { return }

            oltCount = olts.count
            onuTotal = registration.count
            onuCounts = Dictionary(grouping: registration.values, by: { $0 })
                .map { (state: $0.key, count: $0.value.count) }
                .sorted { $0.count > $1.count }
            totalDownstreamBps = olts.compactMap(\.txRateBps).reduce(0, +)
            totalUpstreamBps = olts.compactMap(\.rxRateBps).reduce(0, +)
            lasersOffOLTs = olts.filter(\.isLaserOff)
            lasersOffCount = lasersOffOLTs.count

            if let controllers { controllerCount = controllers }      // else keep last good
            if let alarms {
                severityCounts = Dictionary(grouping: alarms, by: { $0.severity })
                    .map { (severity: $0.key, count: $0.value.count) }
                    .sorted { $0.severity.rank < $1.severity.rank }
            }                                                          // else keep last good

            opticalAvailable = optAvailable
            abnormalRxONUs = abnormal
            abnormalRxCount = abnormal.count
            phase = .loaded
        } catch {
            guard gen == loadGeneration, !Task.isCancelled else { return }
            phase = .failed((error as? APIError)?.localizedDescription ?? error.localizedDescription)
        }
    }
}
