//
//  DataStore.swift
//  USB Meter
//
//  Created by Bogdan Timofte on 03/03/2020.
//  Copyright © 2020 Bogdan Timofte. All rights reserved.
//

import SwiftUI
import Combine
import CoreBluetooth

final class AppData : ObservableObject {
    struct MeterSummary: Identifiable {
        let macAddress: String
        let displayName: String
        let modelSummary: String
        let advertisedName: String?
        let lastSeen: Date?
        let lastConnected: Date?
        let meter: Meter?

        var id: String {
            macAddress
        }
    }

    private var bluetoothManagerNotification: AnyCancellable?
    private var meterStoreObserver: AnyCancellable?
    private var meterStoreCloudObserver: AnyCancellable?
    private let meterStore = MeterNameStore.shared

    init() {
        bluetoothManagerNotification = bluetoothManager.objectWillChange.sink { [weak self] _ in
            self?.scheduleObjectWillChange()
        }
        meterStoreObserver = NotificationCenter.default.publisher(for: .meterNameStoreDidChange)
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.refreshMeterMetadata()
            }
        meterStoreCloudObserver = NotificationCenter.default.publisher(for: .meterNameStoreCloudStatusDidChange)
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.scheduleObjectWillChange()
            }
    }

    let bluetoothManager = BluetoothManager()

    @Published var enableRecordFeature: Bool = true

    @Published var meters: [UUID:Meter] = [UUID:Meter]()

    var cloudAvailability: MeterNameStore.CloudAvailability {
        meterStore.currentCloudAvailability
    }

    func meterName(for macAddress: String) -> String? {
        meterStore.name(for: macAddress)
    }

    func setMeterName(_ name: String, for macAddress: String) {
        meterStore.upsert(macAddress: macAddress, name: name, temperatureUnitRawValue: nil)
    }

    func temperatureUnitPreference(for macAddress: String) -> TemperatureUnitPreference {
        let rawValue = meterStore.temperatureUnitRawValue(for: macAddress) ?? TemperatureUnitPreference.celsius.rawValue
        return TemperatureUnitPreference(rawValue: rawValue) ?? .celsius
    }

    func setTemperatureUnitPreference(_ preference: TemperatureUnitPreference, for macAddress: String) {
        meterStore.upsert(macAddress: macAddress, name: nil, temperatureUnitRawValue: preference.rawValue)
    }

    func registerMeter(macAddress: String, modelName: String?, advertisedName: String?) {
        meterStore.registerMeter(macAddress: macAddress, modelName: modelName, advertisedName: advertisedName)
    }

    func noteMeterSeen(at date: Date, macAddress: String) {
        meterStore.noteLastSeen(date, for: macAddress)
    }

    func noteMeterConnected(at date: Date, macAddress: String) {
        meterStore.noteLastConnected(date, for: macAddress)
    }

    func lastSeen(for macAddress: String) -> Date? {
        meterStore.lastSeen(for: macAddress)
    }

    func lastConnected(for macAddress: String) -> Date? {
        meterStore.lastConnected(for: macAddress)
    }

    var meterSummaries: [MeterSummary] {
        let liveMetersByMAC = Dictionary(uniqueKeysWithValues: meters.values.map { ($0.btSerial.macAddress.description, $0) })
        let recordsByMAC = Dictionary(uniqueKeysWithValues: meterStore.allRecords().map { ($0.macAddress, $0) })
        let macAddresses = Set(recordsByMAC.keys).union(liveMetersByMAC.keys)

        return macAddresses.map { macAddress in
            let liveMeter = liveMetersByMAC[macAddress]
            let record = recordsByMAC[macAddress]

            return MeterSummary(
                macAddress: macAddress,
                displayName: Self.friendlyDisplayName(liveMeter: liveMeter, record: record),
                modelSummary: liveMeter?.deviceModelSummary ?? record?.modelName ?? "Meter",
                advertisedName: liveMeter?.modelString ?? record?.advertisedName,
                lastSeen: liveMeter?.lastSeen ?? record?.lastSeen,
                lastConnected: liveMeter?.lastConnectedAt ?? record?.lastConnected,
                meter: liveMeter
            )
        }
        .sorted { lhs, rhs in
            if lhs.meter != nil && rhs.meter == nil {
                return true
            }
            if lhs.meter == nil && rhs.meter != nil {
                return false
            }
            let byName = lhs.displayName.localizedCaseInsensitiveCompare(rhs.displayName)
            if byName != .orderedSame {
                return byName == .orderedAscending
            }
            return lhs.macAddress < rhs.macAddress
        }
    }

    private func scheduleObjectWillChange() {
        DispatchQueue.main.async { [weak self] in
            self?.objectWillChange.send()
        }
    }

    private func refreshMeterMetadata() {
        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            var didUpdateAnyMeter = false
            for meter in self.meters.values {
                let mac = meter.btSerial.macAddress.description
                let displayName = self.meterName(for: mac) ?? mac
                if meter.name != displayName {
                    meter.updateNameFromStore(displayName)
                    didUpdateAnyMeter = true
                }

                let previousTemperaturePreference = meter.tc66TemperatureUnitPreference
                meter.reloadTemperatureUnitPreference()
                if meter.tc66TemperatureUnitPreference != previousTemperaturePreference {
                    didUpdateAnyMeter = true
                }
            }

            if didUpdateAnyMeter {
                self.scheduleObjectWillChange()
            }
        }
    }
}

extension AppData.MeterSummary {
    var tint: Color {
        switch modelSummary {
        case "UM25C":
            return .blue
        case "UM34C":
            return .yellow
        case "TC66C":
            return Model.TC66C.color
        default:
            return .secondary
        }
    }
}

private extension AppData {
    static func friendlyDisplayName(liveMeter: Meter?, record: MeterNameStore.Record?) -> String {
        if let liveName = liveMeter?.name.trimmingCharacters(in: .whitespacesAndNewlines), !liveName.isEmpty {
            return liveName
        }
        if let customName = record?.customName {
            return customName
        }
        if let advertisedName = record?.advertisedName {
            return advertisedName
        }
        if let recordModel = record?.modelName {
            return recordModel
        }
        if let liveModel = liveMeter?.deviceModelSummary {
            return liveModel
        }
        return "Meter"
    }
}
