import SwiftUI

struct ONUDetailView: View {
    @State private var vm: ONUDetailViewModel
    @State private var showResetConfirm = false
    @State private var showFirmwareSheet = false

    init(connection: MCMSConnection, onuId: String) {
        _vm = State(initialValue: ONUDetailViewModel(connection: connection, onuId: onuId))
    }

    var body: some View {
        @Bindable var vm = vm
        VStack(spacing: 0) {
            Picker("Tab", selection: $vm.tab) {
                ForEach(ONUDetailTab.allCases) { Text($0.rawValue).tag($0) }
            }
            .pickerStyle(.segmented)
            .padding()

            tabContent
        }
        .navigationTitle(vm.onuId)
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                Menu {
                    Button(role: .destructive) { showResetConfirm = true } label: {
                        Label("Reset ONU", systemImage: "arrow.clockwise")
                    }
                } label: {
                    Image(systemName: "ellipsis.circle")
                }
                .disabled(vm.isPerformingAction)
            }
        }
        .sheet(isPresented: $showResetConfirm) {
            ResetConfirmSheet(deviceKind: "ONU", deviceName: vm.onuId) {
                Task { await vm.reset() }
            }
        }
        .sheet(isPresented: $showFirmwareSheet) {
            if let state = vm.liveState {
                FirmwareUpdateSheet(vm: vm, state: state)
            }
        }
        .alert("Operation", isPresented: Binding(
            get: { vm.actionMessage != nil },
            set: { if !$0 { vm.actionMessage = nil } }
        )) {
            Button("OK") { vm.actionMessage = nil }
        } message: {
            Text(vm.actionMessage ?? "")
        }
        .task(id: vm.tab) { await vm.loadCurrentTab() }
        .refreshable { await vm.refresh() }
    }

    @ViewBuilder
    private var tabContent: some View {
        switch vm.phase {
        case .idle, .loading:
            LoadingView()
        case .failed(let msg):
            ErrorStateView(message: msg) { Task { await vm.loadCurrentTab() } }
        case .loaded:
            switch vm.tab {
            case .stats:  overviewView
            case .cpe:    cpeView
            case .config: configView
            case .alarms: alarmsView
            case .logs:   logsView
            }
        }
    }

    // MARK: Stats

    private var overviewView: some View {
        List {
            if let s = vm.liveState {
                statusSection(s)
                firmwareSection(s)
                opticalSection(s)
                uniSection(s)
                trafficSection(s)
                if let updated = MongoTimestamp.displayString(s.lastUpdate) {
                    Section { LabeledContent("Last update", value: updated) }
                }
            }
            historySection
        }
    }

    @ViewBuilder private func statusSection(_ s: ONUStateDoc) -> some View {
        Section("Status") {
            if let reg = s.registrationState { LabeledContent("Registration", value: reg) }
            if let eq = s.equipmentId { LabeledContent("Equipment", value: eq) }
            if let mode = s.ponMode { LabeledContent("PON mode", value: mode) }
            if let temp = s.temperatureC {
                LabeledContent("Temperature", value: "\(temp.formatted(.number.precision(.fractionLength(1)))) °C")
            }
            if let online = s.onlineTime {
                LabeledContent("Online since", value: MongoTimestamp.displayString(online) ?? online)
                if let up = MongoTimestamp.duration(from: online, to: s.lastUpdate) {
                    LabeledContent("Uptime", value: up)
                }
            }
        }
    }

    @ViewBuilder private func firmwareSection(_ s: ONUStateDoc) -> some View {
        Section("Firmware") {
            if let v = s.fwVersion { LabeledContent("Active version", value: v) }
            ForEach(Array(s.fwBankVersions.enumerated()), id: \.offset) { idx, ver in
                LabeledContent("Bank \(idx)" + (s.fwBankPtr == idx ? " · active" : ""),
                               value: ver.isEmpty ? "—" : ver)
            }
            if let st = s.fwUpgradeStatus {
                if let status = st["Status"]?.stringValue { LabeledContent("Upgrade", value: status) }
                if let prog = st["Progress"]?.intValue { LabeledContent("Progress", value: "\(prog)%") }
            }
            Button { showFirmwareSheet = true } label: {
                Label("Update firmware…", systemImage: "arrow.up.circle")
            }
            .disabled(vm.isPerformingAction)
        }
    }

    @ViewBuilder private func opticalSection(_ s: ONUStateDoc) -> some View {
        Section {
            opticalRow("RX optical", s.rxOpticalDBm,
                       green: OpticalThreshold.rxGreenFloor, warn: OpticalThreshold.rxRedFloor)
            opticalRow("TX optical", s.txOpticalDBm,
                       green: OpticalThreshold.txGreenFloor, warn: OpticalThreshold.txGreenFloor)
        } header: {
            Text("Live optical")
        } footer: {
            Text("Green RX ≥ −28 dBm, TX ≥ 3 dBm (user guide §7).")
        }
        Section("FEC error counters") {
            fecRow("ONU pre-FEC",  s.onuPreFEC,  isPost: false)
            fecRow("ONU post-FEC", s.onuPostFEC, isPost: true)
            fecRow("OLT pre-FEC",  s.oltPreFEC,  isPost: false)
            fecRow("OLT post-FEC", s.oltPostFEC, isPost: true)
        }
    }

    @ViewBuilder private func uniSection(_ s: ONUStateDoc) -> some View {
        if !s.uniPorts.isEmpty {
            Section("UNI ports") {
                ForEach(s.uniPorts) { port in
                    HStack {
                        VStack(alignment: .leading, spacing: 2) {
                            Text(port.name).font(.subheadline)
                            if let detail = uniDetail(port) {
                                Text(detail).font(.caption).foregroundStyle(.secondary)
                            }
                        }
                        Spacer()
                        if let enabled = port.enabled {
                            StateBadge(text: enabled ? "enabled" : "disabled",
                                       color: enabled ? .green : .gray)
                        }
                    }
                }
            }
        }
    }

    private func uniDetail(_ port: UNIPort) -> String? {
        if let state = port.state { return state }
        let parts = [port.speed, port.duplex].compactMap { $0 }
        return parts.isEmpty ? nil : parts.joined(separator: " · ")
    }

    @ViewBuilder private func trafficSection(_ s: ONUStateDoc) -> some View {
        if !s.ponServices.isEmpty {
            Section("Traffic") {
                ForEach(s.ponServices) { svc in
                    VStack(alignment: .leading, spacing: 6) {
                        Text(svc.name).font(.subheadline)
                        HStack(alignment: .top) {
                            trafficStat("↓ Rx", rate: svc.rxRateBps, total: svc.rxTotalOctets)
                            trafficStat("↑ Tx", rate: svc.txRateBps, total: svc.txTotalOctets)
                        }
                    }
                    .padding(.vertical, 2)
                }
            }
        }
    }

    private func trafficStat(_ label: String, rate: Double?, total: Double?) -> some View {
        VStack(alignment: .leading, spacing: 2) {
            Text(label).font(.caption2).foregroundStyle(.secondary)
            Text(Format.bps(rate)).font(.callout.monospacedDigit())
            Text("\(Format.bytes(total)) total").font(.caption2).foregroundStyle(.secondary)
        }
        .frame(maxWidth: .infinity, alignment: .leading)
    }

    @ViewBuilder private var historySection: some View {
        Section("History (24h)") {
            LabeledContent("Samples", value: "\(vm.stats.count)")
            if let latest = vm.stats.first, let ts = MongoTimestamp.displayString(latest.timestamp) {
                LabeledContent("Latest sample", value: ts)
            }
        }
        if let latest = vm.stats.first, !latest.numericMetrics.isEmpty {
            Section("Latest counters") {
                ForEach(latest.numericMetrics, id: \.key) { metric in
                    LabeledContent(metric.key) {
                        Text(metric.value.formatted()).monospacedDigit()
                    }
                }
            }
        }
    }

    /// Optical level row, tinted by the user-guide thresholds (§7.3). `green` is
    /// the full-pass floor, `warn` the margin floor; below `warn` is red.
    private func opticalRow(_ title: String, _ value: Double?, green: Double, warn: Double) -> some View {
        LabeledContent(title) {
            if let value {
                Text("\(value.formatted(.number.precision(.fractionLength(2)))) dBm")
                    .monospacedDigit()
                    .foregroundStyle(value >= green ? .green : (value >= warn ? .yellow : .red))
            } else {
                Text("—").foregroundStyle(.secondary)
            }
        }
    }

    /// FEC counter row. Post-FEC > 0 means uncorrected errors reached the user
    /// (red); pre-FEC > 0 is informational; all-zero is healthy (§7.1–7.2).
    private func fecRow(_ title: String, _ value: Double?, isPost: Bool) -> some View {
        LabeledContent(title) {
            if let value {
                Text(Int(value).formatted())
                    .monospacedDigit()
                    .foregroundStyle(isPost && value > 0 ? .red : (value > 0 ? .secondary : .green))
            } else {
                Text("—").foregroundStyle(.secondary)
            }
        }
    }

    // MARK: CPE (DHCP leases)

    private var cpeView: some View {
        Group {
            if vm.cpes.isEmpty {
                ContentUnavailableView("No CPEs", systemImage: "laptopcomputer.slash")
            } else {
                List(vm.cpes) { cpe in
                    Section {
                        if let ip = cpe.ipv4 {
                            LabeledContent("IPv4") {
                                HStack(spacing: 6) {
                                    Text(ip).monospacedDigit()
                                    if let state = cpe.dhcpState { dhcpBadge(state) }
                                }
                            }
                            if let server = cpe.dhcpServer { LabeledContent("DHCP server", value: server) }
                            if let lease = cpe.dhcpLeaseSeconds { LabeledContent("Lease", value: leaseText(lease)) }
                            if let renew = MongoTimestamp.displayString(cpe.dhcpLastSuccess) {
                                LabeledContent("Last renew", value: renew)
                            }
                        }
                        if let ip6 = cpe.ipv6 {
                            LabeledContent("IPv6") {
                                HStack(spacing: 6) {
                                    Text(ip6).font(.callout.monospaced())
                                    if let state = cpe.dhcpv6State { dhcpBadge(state) }
                                }
                            }
                            if let prefix = cpe.ipv6Prefix { LabeledContent("Delegated prefix", value: prefix) }
                            if let valid = MongoTimestamp.displayString(cpe.ipv6ValidLifetime) {
                                LabeledContent("Valid until", value: valid)
                            }
                        }
                        if let circuit = cpe.circuitId { LabeledContent("Circuit ID", value: circuit) }
                        if !cpe.hasLease { Text("No active lease.").foregroundStyle(.secondary) }
                    } header: {
                        Text(cpe.mac ?? cpe.id)
                    }
                }
            }
        }
    }

    private func dhcpBadge(_ state: String) -> some View {
        let ok = state.lowercased().contains("bound") || state.lowercased() == "ack"
        return StateBadge(text: state, color: ok ? .green : .orange)
    }

    private func leaseText(_ seconds: Int) -> String {
        if seconds % 86400 == 0 { return "\(seconds / 86400)d" }
        if seconds % 3600 == 0 { return "\(seconds / 3600)h" }
        return "\(seconds / 60)m"
    }

    // MARK: Config

    private var configView: some View {
        List {
            if let config = vm.config {
                if let name = config.netconfName {
                    Section("Identity") { LabeledContent("NETCONF name", value: name) }
                }
                Section("Raw document") {
                    JSONTreeView(value: config.raw)
                }
            } else {
                Text("No configuration loaded.").foregroundStyle(.secondary)
            }
        }
    }

    // MARK: Alarms

    private var alarmsView: some View {
        Group {
            if vm.alarms.isEmpty {
                ContentUnavailableView("No alarms", systemImage: "bell.slash")
            } else {
                List(vm.alarms) { alarm in
                    VStack(alignment: .leading, spacing: 4) {
                        HStack {
                            SeverityBadge(severity: alarm.severity)
                            Spacer()
                            if let at = MongoTimestamp.displayString(alarm.raisedAt) {
                                Text(at).font(.caption).foregroundStyle(.secondary)
                            }
                        }
                        Text(alarm.message ?? alarm.id).font(.subheadline)
                    }
                    .padding(.vertical, 2)
                }
                .listStyle(.plain)
            }
        }
    }

    // MARK: Logs

    private var logsView: some View {
        Group {
            if vm.logs.isEmpty {
                ContentUnavailableView("No logs", systemImage: "doc.text")
            } else {
                List(vm.logs) { log in
                    VStack(alignment: .leading, spacing: 4) {
                        HStack {
                            SeverityBadge(severity: log.severity)
                            Spacer()
                            if let t = MongoTimestamp.displayString(log.time) {
                                Text(t).font(.caption).foregroundStyle(.secondary)
                            }
                        }
                        Text(log.message ?? log.id).font(.caption).foregroundStyle(.primary)
                    }
                    .padding(.vertical, 2)
                }
                .listStyle(.plain)
            }
        }
    }
}

/// Minimal recursive viewer for a `JSONValue` (read-only). Renders nested
/// objects/arrays as disclosure groups so the full Mongo document is browsable
/// before specific fields are typed.
struct JSONTreeView: View {
    let value: JSONValue
    var key: String? = nil

    var body: some View {
        switch value {
        case .object(let dict):
            DisclosureGroup(key ?? "{ }") {
                ForEach(dict.keys.sorted(), id: \.self) { k in
                    JSONTreeView(value: dict[k]!, key: k)
                }
            }
        case .array(let arr):
            DisclosureGroup(key.map { "\($0) [\(arr.count)]" } ?? "[ \(arr.count) ]") {
                ForEach(Array(arr.enumerated()), id: \.offset) { idx, item in
                    JSONTreeView(value: item, key: "\(idx)")
                }
            }
        default:
            LabeledContent(key ?? "value") {
                Text(value.compactDescription)
                    .font(.callout.monospaced())
                    .foregroundStyle(.secondary)
                    .multilineTextAlignment(.trailing)
            }
        }
    }
}
