//
//  GMFGBluetoothDebugger.swift
//  MVM5G
//
//  Created by Matt Bruce on 3/16/22.
//  Copyright © 2022 Kyle. All rights reserved.
//

import Foundation
import CoreBluetooth

//Delegate used to receive updates from the debugger
//during a onChange() debugger event
protocol BluetoothDebuggerDelegate: AnyObject {
    func bluetoothDebuggerUpdate(message: String)
}

class BluetoothDebugger: NSObject {
    weak var delegate: BluetoothDebuggerDelegate?
    var deviceId: String
    var config: BluetoothConfigModel
    var bluetoothState: CBManagerState = .unknown { didSet { onChange() } }
    var bluetoothStatus: Bool = false { didSet { onChange() } }
    var isDevicePaired: Bool = false { didSet{ onChange() } }
    var isDeviceActivated: Bool = false{ didSet{ onChange() } }
    var isBluetoothPermissionDenied: Bool = false { didSet{ onChange() } }
    var deviceDiscovered: String? { didSet{ onChange() } }
    var deviceDiscoveredNameFailed: Set<String> = [] { didSet{ onChange() } }
    var deviceMismatch: Set<String> = [] { didSet{ onChange() } }
    var signalStatus: String? { didSet{ onChange() } }
    var otherErrors: Set<String> = [] { didSet{ onChange() } }
    var logs: Set<String> = [] { didSet { onChange() } }
    
    //computed properties
    var deviceInfo: String{
        let value = """
        deviceId: \(deviceId)\r
        bluetoothState: \(bluetoothState.description)\r
        bluetoothStatus: \(bluetoothStatus)\r
        bluetoothPermissionDenied: \(isBluetoothPermissionDenied)\r
        devicePaired: \(isDevicePaired)\r
        deviceActivated: \(isDeviceActivated)\r
        """
        return value
    }
    
    var discoveryInfo: String? {
        var value: [String] = []
        
        if let deviceDiscovered = deviceDiscovered {
            value.append("deviceDiscovered: \(deviceDiscovered)\r")
        }
        
        if deviceDiscoveredNameFailed.isNotEmpty {
            value.append("deviceDiscoveredNameFailed: \r\(deviceDiscoveredNameFailed.toString())")
        }
        
        if deviceMismatch.isNotEmpty {
            value.append("deviceMismatch: \(deviceMismatch.toString())")
        }

        return value.isNotEmpty ? value.joined(separator: "") : nil
    }
    
    var otherErrorsInfo: String?  {
        guard otherErrors.isNotEmpty else { return nil }
        return "\rErrors Occurred:\r\(otherErrors.toString())"
    }
    
    var logsInfo: String {
        return "\rlogs:\r\(logs.toString())"
    }
    
    var configInfo: String {
        guard let string = config.toJSONString() else {
            return "\rConfig:\rdecodeError"
        }
        return "\rConfig:\r\(string)"
    }
    
    override var description: String {
        var descriptions: [String] = [deviceInfo]
                
        //optional messages
        if let discoveryInfo = discoveryInfo {
            descriptions.append(discoveryInfo)
        }
        
        if let firmwareInfo = firmwareInfo {
            descriptions.append(firmwareInfo)
        }

        if let signalStatus = signalStatus {
            descriptions.append(signalStatus)
        }
        
        if let publicInternetAccessStatusInfo = publicInternetAccessStatusInfo {
            descriptions.append(publicInternetAccessStatusInfo)
        }
        
        if let speedTestStatusInfo = speedTestStatusInfo {
            descriptions.append(speedTestStatusInfo)
        }
        
        if let routerWifiInfo = routerWifiInfo {
            descriptions.append(routerWifiInfo)
        }
        
        if let signalUpdateInfo = signalUpdateInfo {
            descriptions.append(signalUpdateInfo)
        }
        
        if let signalUpdateInfo = signalUpdateCBandInfo {
            descriptions.append(signalUpdateInfo)
        }
        
        if let otherErrorsInfo = otherErrorsInfo {
            descriptions.append(otherErrorsInfo)
        }
        
        descriptions.append(contentsOf: [logsInfo, configInfo])

        return descriptions.joined(separator: "")
    }
    
    required init(config: BluetoothConfigModel, delegate: BluetoothDebuggerDelegate?){
        self.deviceId = config.advertisedData
        self.config = config
        self.delegate = delegate
        super.init()
        self.onChange()
    }
    
    //methods
    func onChange(){
        delegate?.bluetoothDebuggerUpdate(message: description)
    }
    
    func addLog(message: String){
        logs.insert(message)
    }
    
    func addOtherError(error: String){
        otherErrors.insert(error)
    }
        
    func addDiscoveredNameFailures(foundNames: [String]) {
        foundNames.forEach { name in
            deviceDiscoveredNameFailed.insert(name)
        }
    }
        
    func addDeviceIMEIMismatch(found: String) {
        deviceMismatch.insert(found)
    }
        
    func updateCurrentSignalStatus(_ status: MFFGHSSignalStatus, strengthCheckStatus: MFFGHSSignalStrengthCheckStatus, rssi: Double?) {
        var rssiValue = "none"
        if let rssi = rssi {
            rssiValue = "\(rssi)"
        }
        signalStatus = "signalStatus: status: \(status) strengthCheck: \(strengthCheckStatus) rssi: \(rssiValue)\r"
    }
    
    //Handler Delegates Properties
    
    //GMFGSpeedTestDelegate
    var speedTestStatus: GMFGSpeedTestUpdate? { didSet { onChange() } }
    var speedTestStatusInfo: String? {
        guard let status = speedTestStatus else { return nil }
        return "speedTestStatusInfo: \(status)\r"
    }
    
    //GMFGRouterWifiDelegate
    var routerWifiCredentials: GMFGRouterWifiInfo? { didSet { onChange() } }
    var routerWifiStatus: GMFGRouterWifiStatus? { didSet { onChange() } }
    var routerWifiInfo: String? {
        var value = ""
        if let routerWifiCredentials = routerWifiCredentials {
            value = """
            routerWifiCredentials:\r
            - ssid: \(routerWifiCredentials.ssid)\r
            - password: \(routerWifiCredentials.password)\r
            """
        }
        if let routerWifiStatus = routerWifiStatus {
            value = value + """
            routerWifiStatus: \(routerWifiStatus)\r
            """
        }
        return value
    }

    //GMFGPublicInternetAccessDelegate
    var publicInternetAccessStatus: GMPublicInternetAccessStatus? { didSet { onChange() } }
    var publicInternetAccessStatusInfo: String? {
        guard let status = publicInternetAccessStatus else { return nil }
        return "publicInternetAccessStatus: \(status)\r"
    }
    
    //GMFGFotaDelegate
    var firmwareResponse: GMFotaResponse? { didSet { onChange() } }
    var isCheckingFirmware: Bool? { didSet { onChange() } }
    var firmwareInfo: String? {
        var value: [String] = []
        if let isCheckingFirmware = isCheckingFirmware {
            value.append("checkingFirmware: \(isCheckingFirmware)\r")
        }
        if let fota = firmwareResponse {
            value.append("""
            firmwareStatus: \(fota.status)\r
            firmwareVersion: \(fota.version)\r
            firmwareError: \(fota.errorMsg ?? "none")\r
            """)
        }
        return value.isNotEmpty ? value.joined(separator: "") : nil
    }
    
    //GMFG5GSignalDelegate
    var signalUpdate: GMFG5GSignalUpdate? { didSet { onChange() } }
    var signalUpdateInfo: String? {
        guard let signalUpdate = signalUpdate else { return nil }
        return "\r5G Signal Update: \r\(signalUpdate)\r"
    }

    //GMFG5GSignalDelegate
    var signalUpdateCBand: GMFG5GCBandSignalUpdate? { didSet { onChange() } }
    var signalUpdateCBandInfo: String? {
        guard let signalUpdate = signalUpdateCBand, let results = signalUpdate.rawResults else { return nil }
        return "\r5G C-Band Signal Update: \r\(results)\r"
    }

}

//Handler Delegate Implementations
extension BluetoothDebugger: GMFGSpeedTestDelegate {
    func didStartSpeedTest() {
        //BLELog has this
    }
    
    func didStopSpeedTest() {
        //BLELog has this
    }

    func speedTestStatus(status: GMFGSpeedTestUpdate) {
        speedTestStatus = status
    }
}

extension BluetoothDebugger: GMFGRouterWifiDelegate {
    func routerWifi(credentials: GMFGRouterWifiInfo) {
        routerWifiCredentials = credentials
    }
    
    func routerWifiStatus(status: GMFGRouterWifiStatus) {
        routerWifiStatus = status
    }
}

extension BluetoothDebugger: GMFGPublicInternetAccessDelegate {
    func publicInternetAccessStatus(status: GMPublicInternetAccessStatus) {
        publicInternetAccessStatus = status
    }
}

extension BluetoothDebugger: GMFGFotaDelegate {
    func fotaStatus(statusResponse: GMFotaResponse) {
        firmwareResponse = statusResponse
    }
    
    func unknownResponse(rawResponse: [String : Any]?) {
        let response: String = rawResponse == nil ? "none" : "\(rawResponse!)"
        addLog(message: "unknown response: \(response)")
    }
}

extension BluetoothDebugger: GMFG5GSignalDelegate {
    
    func updated5GSignal(_ update: GMFG5GSignalUpdate){
        signalUpdate = update
    }
    
    func signalNotReady() {
        signalUpdate = nil
        addLog(message: "5G Signal Not Ready")
    }

}

extension BluetoothDebugger: GMFG5GCBandSignalDelegate {
    
    func updated5GCBandSignal(_ update: GMFG5GCBandSignalUpdate){
        signalUpdateCBand = update
    }
    
    func signal5GCBandNotReady() {
        signalUpdateCBand = nil
        addLog(message: "5G C-Band Signal Not Ready")
    }
}

extension CBManagerState: CustomStringConvertible {
    public var description: String {
        switch self {
        case .unknown:
            return "unknown"
        case .resetting:
            return "resetting"
        case .unsupported:
            return "unsupported"
        case .unauthorized:
            return "unauthorized"
        case .poweredOff:
            return "poweredOff"
        case .poweredOn:
            return "poweredOn"
        @unknown default:
            return "unknown"
        }
    }
}

extension Collection {
    fileprivate func toString() -> String {
        return self.map{" - \($0)\r"}.joined(separator: "")
    }
}
