import UIKit
import NearbyInteraction
import os.log
import UserNotifications
import BackgroundTasks
import CoreBluetooth

@available(iOS 16.0, *)
class UWBViewController: UIViewController {
    var dataChannel = DataCommunicationChannel()
    var niSession = NISession()
    var configuration: NINearbyAccessoryConfiguration?
    var accessoryConnected = false
    var connectedAccessoryName: String?
    
    var bgTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 123)
    
    // Mappo un token di individuazione accessorio con un nome.
    var accessoryMap = [NIDiscoveryToken: String]()
    var nameValue: String?
    
    let logger = os.Logger(subsystem: "com.alexandrovassallo.SMAC2", category:"UWBViewController")

    @IBOutlet weak var connectionStateLabel: UILabel!
    @IBOutlet weak var uwbStateLabel: UILabel!
    @IBOutlet weak var infoLabel: UILabel!
    @IBOutlet weak var distanceLabel: UILabel!
    @IBOutlet weak var actionButton: UIButton!
    @IBOutlet var InfoView: UIView!
    //@IBOutlet weak var buttonConnected: UIImageView!
    @IBOutlet weak var Conn: UIImageView!
    
    private let notificationManager = NotificationManager()
    

// MARK: - Gestione iniziale
    override func viewDidLoad() {
        super.viewDidLoad()
        print("Step: 1 - viewDidLoad")
        //imposto un delegato per la sessione di aggiornamento del framework
        niSession.delegate = self
        Conn.isHidden = true

        // preparo il canale di comunicazione tra il chip e l'accessorio di terze parti
        dataChannel.accessoryConnectedHandler = accessoryConnected
        dataChannel.accessoryDisconnectedHandler = accessoryDisconnected
        dataChannel.accessoryDataHandler = accessorySharedData
        dataChannel.accessoryNIServiceDiscoveredHandler = accessoryNIServiceDiscovered
        dataChannel.start()
        
        updateInfoLabel(with: "Ricerca Antenna...")
        notificationManager.sendNotification(title: "SMAC", subtitle: "Controllo Background Abilitato", body: " ", badge: 0, delayInterval: nil)
        
        let state = UIApplication.shared.applicationState
        if state == .active {
           print("I'm active")
        }
        else if state == .inactive {
           print("I'm inactive")
        }
        else if state == .background {
           print("I'm in background")
        }
    }
//MARK: - Gestione Bottone
    @IBAction func buttonAction(_ sender: Any) {
        print("Step: 2 - ButtonActionPressed")
        //updateInfoLabel(with: "Premi il pulsante verde")

        let msg = Data([Message.MessageId.initialize.rawValue])
        sendDataToAccessory(msg)

    }

//MARK: - Gestione del canale di comunicazione - CoreBluetooth

    func accessorySharedData(data: Data, accessoryName: String) {
        //L'accessorio inizia ogni messaggio con un id in byte: Controllo sulla lunghezza del messaggio
        print("Step: 3 - AccessorySharedData")
        
        
        if data.count < 1 {
            updateInfoLabel(with: "La lunghezza dei dati condivisi accessori era inferiore a 1.")
            return
        }

        // Assegna il primo byte che è l'identificatore del messaggio.
        guard let messageId = Message.MessageId(rawValue: data.first!) else {
            fatalError("\(data.first!) non è un Message Id Valido.")
        }

        // Gestione dell'id del messaggio.
        switch messageId{

        case .accessoryConfigurationData:
            print("Step: 4 - AccessoryConfigurationData")
            // Accedo ai dati del messaggio saltando l'identificatore del messaggio.
            assert(data.count > 1)
            let message = data.advanced(by: 1)
            setupAccessory(message, name: accessoryName)

        case .accessoryUwbDidStart:
            print("Step: 5 - accessoryUwbDidStart")
            handleAccessoryUwbDidStart()

        case .accessoryUwbDidStop:
            print("Step: 6 - accessoryUwbDidStop")
            handleAccessoryUwbDidStop()

        case .accessoryPassageOpening:
            handleAccessoryUwbOpenAccess()
            
        case .configureAndStart:
            print("Step: 7 - configureAndStart")
            fatalError("Errore in 'configureAndStart'.")

        case .initialize:
            print("Step: 8 - Initialize")
            fatalError("Errore in'initilize'.")

        case .stop:
            print("Step: 9 - stop")
            fatalError("Errore in 'stop'.")

        }
    }


    func accessoryConnected(name: String) {
        print("Step: 10 - accessoryConnected")
        accessoryConnected = true
        connectedAccessoryName = name

        //buttonConnected.isHidden = false
        //actionButton.isEnabled = true
        connectionStateLabel.text = "Connesso"
        
        //updateInfoLabel(with: "Premi il pulsante verde")
        let msg = Data([Message.MessageId.initialize.rawValue])
        sendDataToAccessory(msg)
    }

    func accessoryDisconnected() {
        print("Step: 11 - accessoryDisconnected")
        accessoryConnected = false
        //actionButton.isEnabled = true
        connectedAccessoryName = nil
        connectionStateLabel.text = "Non Connesso"
        updateInfoLabel(with: "Antenna Disconnessa")
    }
    
    func accessoryNIServiceDiscovered() {
        logger.info("Accessory NI Service discovered")
    
        //updateInfoLabel(with: "Requesting configuration data from accessory")
        let msg = Data([Message.MessageId.initialize.rawValue])
        sendDataToAccessory(msg)
    }

    // Gestione dei messaggi degli accessori
    func setupAccessory(_ configData: Data, name: String) {
        print("Step: 12 - setupAccessory")
        updateInfoLabel(with: "Ricevo dati: '\(name)'.")
        
        do {
            let peerIdentifier = dataChannel.discoveredPeripheral!.identifier
            print("peerIdentifier: \(peerIdentifier)")
            logger.debug("Bluetooth peer name '\(name)', identifier '\(peerIdentifier)'")
            
            configuration = try NINearbyAccessoryConfiguration( accessoryData: configData, bluetoothPeerIdentifier: peerIdentifier)
            
        } catch {
            // Interrompo e visualizzo il problema perché i dati in entrata non sono validi.
            updateInfoLabel(with: "Impossibile creare NINearbyAccessoryConfiguration per '\(name)'. Errore: \(error)")
            return
        }

        // Memorizzo nella cache il token per gli aggiornamenti con questo accessorio.
        cacheToken(configuration!.accessoryDiscoveryToken, accessoryName: name)
        niSession.run(configuration!)
    }

    func handleAccessoryUwbDidStart() {
        print("Step: 13 - handleAccessoryUwbDidStart")
        updateInfoLabel(with: "Scambio di Informazioni In corso...")
        //actionButton.isEnabled = true
        //actionButton.setTitle("Chiudi sessione", for: .normal)
        self.uwbStateLabel.text = "ON"

    }

    func handleAccessoryUwbDidStop() {
        print("Step: 14 - handleAccessoryUwbDidStop")
        //updateInfoLabel(with: "Sessione con Antenna Chiusa.")

        if accessoryConnected {
            //updateInfoLabel(with: "Scambio di Informazioni In corso...")
            //actionButton.isEnabled = true
            //actionButton.setTitle("Chiudi sessione", for: .normal)
            self.uwbStateLabel.text = "ON"
            Conn.isHidden = false
            //actionButton.isEnabled = true
        }
        self.uwbStateLabel.text = "OFF"
    }
    
    
    func handleAccessoryUwbOpenAccess() {

        logger.info("Apertura del varco in corso")
        updateInfoLabel(with: "Varco Aperto")
        if accessoryConnected {
            updateInfoLabel(with: "Scambio di Informazioni In corso...")
            //actionButton.isEnabled = true
            //actionButton.setTitle("Chiudi sessione", for: .normal)
            self.uwbStateLabel.text = "ON"
            
            //actionButton.isEnabled = true
        }
        self.uwbStateLabel.text = "OFF"
    }

}

// MARK: NISessionDelegate: monitoro e reagisco agli aggiornamenti di sessione.
@available(iOS 16.0, *)
extension UWBViewController: NISessionDelegate {
    
    

    func session(_ session: NISession, didGenerateShareableConfigurationData shareableConfigurationData: Data, for object: NINearbyObject) {
        print("Step: 15 - Session:didGenerateSharableConfigurationData")
        guard object.discoveryToken == configuration?.accessoryDiscoveryToken else { return }

        // Preparazione all'invio di un messaggio all'accessorio.
        var msg = Data([Message.MessageId.configureAndStart.rawValue])
        msg.append(shareableConfigurationData)

        let str = msg.map { String(format: "0x%02x, ", $0) }.joined()
        logger.info("Invio di byte di configurazione condivisibili: \(str)")

        let accessoryName = accessoryMap[object.discoveryToken] ?? "Non disponibile"

        // Invio del messaggio all'accessorio
        sendDataToAccessory(msg)
        updateInfoLabel(with: "Invio dati configurazione '\(accessoryName)'.")
    }


    func session(_ session: NISession, didUpdate nearbyObjects: [NINearbyObject]) {
 
            guard let accessory = nearbyObjects.first else { return }
            guard let distance = accessory.distance else { return }
            //guard let name = accessoryMap[accessory.discoveryToken] else { return }
            Conn.isHidden = false
        
            //self.distanceLabel.text = String(format: "Distanza %@ %0.1f M", name, distance)
            self.distanceLabel.text = String(format: "%0.1f \nMetri", distance)
            self.distanceLabel.sizeToFit()

            print("Distanza: \(distance)")
        
            //Pensare ad algoritmi di temporizzazione per migliorare l'esperienza utente
        
                if(distance <= 0.3){
                    print("Varco aperto in background")
                    
                    //let msg = Data([Message.MessageId.accessoryPassageOpening.rawValue])
                    //sendDataToAccessory(msg)
                    updateInfoLabel(with: "Apertura varco.")
                    
                    self.distanceLabel.text = "Varco Aperto"
                    self.distanceLabel.sizeToFit()
                    self.infoLabel.text = "Ingresso abilitato"
                    
                    //Sospendo la connsessione
                    let msg2 = Data([Message.MessageId.stop.rawValue])
                    sendDataToAccessory(msg2)
                }
        
        
        if UIApplication.shared.applicationState == .background {
            print("Background")
            guard let accessory = nearbyObjects.first else { return }
            guard let distance = accessory.distance else { return }
            guard let name = accessoryMap[accessory.discoveryToken] else { return }
            Conn.isHidden = false
        
            //self.distanceLabel.text = String(format: "Distanza %@ %0.1f M", name, distance)
            self.distanceLabel.text = String(format: "%0.1f \nMetri", distance)
            self.distanceLabel.sizeToFit()

            print("Distanza background: \(distance)")
        
            //Pensare ad algoritmi di temporizzazione per migliorare l'esperienza utente
                if(distance <= 0.3){
                    print("Varco aperto in background")
                    logger.info("Richiesta di apertura Varco:\(name)")
                    //let msg = Data([Message.MessageId.accessoryPassageOpening.rawValue])
                    //sendDataToAccessory(msg)
                    updateInfoLabel(with: "Apertura varco: '\(name)'.")
                    
                    //self.distanceLabel.text = "Varco Aperto"
                    //self.distanceLabel.sizeToFit()
                    updateInfoLabel(with: "Ingresso Abilitato")
                    
                    
                    //Sospendo la connsessione
                    let msg2 = Data([Message.MessageId.stop.rawValue])
                    sendDataToAccessory(msg2)

                }
        }
    }
    
    

    func session(_ session: NISession, didRemove nearbyObjects: [NINearbyObject], reason: NINearbyObject.RemovalReason) {
        
        print("Step: 17 - session:didRemove")
        sendDataToAccessory(Data([Message.MessageId.stop.rawValue]))
        
        // Riprova la sessione solo se il peer è scaduto.
        guard reason == .timeout else { return }
        updateInfoLabel(with: "Sessione con '\(self.connectedAccessoryName ?? "Antenna")' timed out.")

        // La sessione viene eseguita con un accessorio.
        guard let accessory = nearbyObjects.first else { return }

        // Cancella lo stato accessorio dell'app.
        accessoryMap.removeValue(forKey: accessory.discoveryToken)

        
        if shouldRetry(accessory) {
            sendDataToAccessory(Data([Message.MessageId.stop.rawValue]))
            sendDataToAccessory(Data([Message.MessageId.initialize.rawValue]))
        }
    }
    


    func sessionWasSuspended(_ session: NISession, nearbyObjects: [NINearbyObject]) {
        print("Step: 18 - sessionWasSuspend")
     
        updateInfoLabel(with: "Connessione sospesa.")
        let msg = Data([Message.MessageId.stop.rawValue])
        sendDataToAccessory(msg)
        
    }
    

    func sessionSuspensionEnded(_ session: NISession, nearbyObjects: [NINearbyObject]) {
        print("Step: 19 - sessionSuspensionEnded")
   
        print("Dentro SuspensionEnded")
        updateInfoLabel(with: "Sessione Sospesa.")

        // Al termine della sospensione, riavvio la configurazione con l'accessorio.
        let msg = Data([Message.MessageId.initialize.rawValue])
        sendDataToAccessory(msg)
    }

    func session(_ session: NISession, didInvalidateWith error: Error) {
        print("Step: 20 - session:didInvalidateWith")
        switch error {
        case NIError.invalidConfiguration:
            // Eseguire il debug dei dati accessori per garantire un formato previsto.
            updateInfoLabel(with: "Configurazione non valida")
        case NIError.userDidNotAllow:
            handleUserDidNotAllow()
        default:
            handleSessionInvalidation()
        }
    }
}

// MARK: - Funzioni Accessorie

@available(iOS 16.0, *)
extension UWBViewController {

    //Aggiornamento delle informazioni nella Label di controllo
    func updateInfoLabel(with text: String) {
        self.infoLabel.text = text
        self.distanceLabel.sizeToFit()
        logger.info("\(text)")
    }

    func sendDataToAccessory(_ data: Data) {
        print("Step: 21 - sendDatatoAccessory")
        do {
            try dataChannel.sendData(data)
        } catch {
            updateInfoLabel(with: "Errore invio dati: \(error)")
        }
    }

    func handleSessionInvalidation() {
        print("Step: 22 - handleSessionInvalidation")
        updateInfoLabel(with: "Sessione invalida Riavvia App.")

        // Chiedo l'interuuzione della comunicazione con l'accessorio
        sendDataToAccessory(Data([Message.MessageId.stop.rawValue]))

        // Sostituisco la sessione invalidata con una nuova.
        self.niSession = NISession()
        self.niSession.delegate = self

        // chiedo l'inizializzazione con l'accessorio
        sendDataToAccessory(Data([Message.MessageId.initialize.rawValue]))
    }

    func shouldRetry(_ accessory: NINearbyObject) -> Bool {
        if accessoryConnected {
            return true
        }
        return false
    }

    func cacheToken(_ token: NIDiscoveryToken, accessoryName: String) {
        accessoryMap[token] = accessoryName
    }

    func handleUserDidNotAllow() {
        // A partire da iOS 15, stato di accesso persistente in Impostazioni.
        updateInfoLabel(with: "Richiesto il permesso con l'interazione UWB. Vai su Impostazioni")

        // Crea un avviso per richiedere all'utente di accedere alle Impostazioni.
        let accessAlert = UIAlertController(title: "Accesso Richiesto",
                                            message: """
                                            L'app SMAC richiede l'accesso alle interazioni nelle vicinanze per gestire l'accesso ai varchi aziendali.

                                            """,
                                            preferredStyle: .alert)
        accessAlert.addAction(UIAlertAction(title: "Annulla", style: .cancel, handler: nil))
        accessAlert.addAction(UIAlertAction(title: "Vai alle Impostazioni", style: .default, handler: {_ in
            // Porto l'utente alle impostazioni dell'app.
            if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
                UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)
            }
        }))

        // Preimpostare l'avviso di accesso.
        present(accessAlert, animated: true, completion: nil)
    }
}
