import Foundation
import Flutter
import MultipeerConnectivity

class NearbyManager: NSObject {
    var device: NearbyDevice!
    var advertiser: MCNearbyServiceAdvertiser!
    var browser: MCNearbyServiceBrowser!
    var invitationHandlers: [String: ((Bool, MCSession?) -> Void)] = [:]
    
    
    func initialize(for deviceName: String? = nil, result: @escaping FlutterResult) {
        self.device = MyDeviceDataGenerator.generate(name: deviceName)
        
        self.advertiser = MCNearbyServiceAdvertiser(
            peer: self.device.peerID,
            discoveryInfo: self.device.toDictionary(),
            serviceType: SERVICE_TYPE
        )
        self.advertiser.delegate = self
        
        self.browser = MCNearbyServiceBrowser(peer: self.device.peerID, serviceType: SERVICE_TYPE)
        self.browser.delegate = self
        
        result(true)
    }
    
    func getSavedDeviceName(result: @escaping FlutterResult) {
        result(Archiver.getName())
    }
    
    func getCurrentDevice(result: @escaping FlutterResult) {
        result(device.toDartFormat())
    }
    
    func openServicesSettings(result: @escaping FlutterResult) {
        if let url = URL(string:UIApplication.openSettingsURLString) {
            if UIApplication.shared.canOpenURL(url) {
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            }
        }
        result(true)
    }
    
    func startAdvertising(result: @escaping FlutterResult) {
        self.advertiser.startAdvertisingPeer()
        result(true)
    }
    
    func startBrowsing(result: @escaping FlutterResult) {
        self.browser.startBrowsingForPeers()
        result(true)
    }
    
    func stopAdvertising(result: @escaping FlutterResult) {
        self.advertiser.stopAdvertisingPeer()
        NearbyDevicesStore.instance.clear()
        result(true)
    }
    
    func stopBrowsing(result: @escaping FlutterResult) {
        self.browser.stopBrowsingForPeers()
        NearbyDevicesStore.instance.clear()
        result(true)
    }
    
    func getPeers(result: @escaping FlutterResult) {
        result(NearbyDevicesStore.instance.toDartFormat())
    }
    
    func invite(for deviceId: String, result: @escaping FlutterResult) {
        do {
            let device = NearbyDevicesStore.instance.find(for: deviceId)
            if let requireDevice = device {
                let nearbySession = requireDevice.createSession(for: self.device.peerID)
                self.browser.invitePeer(
                    requireDevice.peerID,
                    to: nearbySession.session,
                    withContext: try JSONSerialization.data(withJSONObject:["displayName": self.device.name]),
                    timeout: 0
                )
                result(true)
            }
        } catch let error {
            Logger.error(message: error.localizedDescription)
            result(false)
        }
    }
    func acceptInvite(for deviceId: String, result: @escaping FlutterResult) {
        let device = NearbyDevicesStore.instance.find(for: deviceId)
        if let requireDevice = device {
            let nearbySession = requireDevice.createSession(for: self.device.peerID)
            self.invitationHandlers[deviceId]?(true, nearbySession.session)
            result(true)
        }
    }
    
    func disconnect(for deviceId: String, result: @escaping FlutterResult) {
        let device = NearbyDevicesStore.instance.find(for: deviceId)
        device?.deleteSession()
        result(true)
    }
    
    func send(for content: NearbyMessageContent, with receiverId: String, result: @escaping FlutterResult) {
        let device = NearbyDevicesStore.instance.find(for: receiverId)

        do {
            if let requireDevice = device {
                let message = NearbyMessage(content: content, senderName: self.device.name, senderPeerID: self.device.peerID)
                
                if (content is NearbyMessageFilesRequest) {
                   NearbyRequestsStore.instance.add(request: message.content as! NearbyMessageFilesRequest)
               }
                try requireDevice.session?.session?.send(
                    try JSONSerialization.data(withJSONObject: message.toDictionary()),
                    toPeers: [requireDevice.peerID],
                    with: MCSessionSendDataMode.reliable
                )
            }
        } catch let error {
            Logger.error(message: error.localizedDescription)
        }
        
        result(true)
    }
    
    func sendFiles(id: String, paths: [String], with receiverId: String) {
        do {
            let device = NearbyDevicesStore.instance.find(for: receiverId)
            if let requireDevice = device {
                let command = NearbyStartCommand(
                    id: id,
                    senderName: self.device.name,
                    filesCount: paths.count
                ).toDictionary()

                try requireDevice.session?.session?.send( 
                    try JSONSerialization.data(withJSONObject: command),
                    toPeers: [requireDevice.peerID],
                    with: MCSessionSendDataMode.reliable
                )

                for path in paths {
                     let url = URL(fileURLWithPath: path)
                        if FileManager.default.fileExists(atPath: url.path) {
                            requireDevice.session?.session?.sendResource(
                                at: url,
                                withName: url.lastPathComponent,
                                toPeer: requireDevice.peerID
                            )
                        } else {
                            Logger.error(message: "File does not exist: " + path)
                        }
                    
                }
            }
        }  catch let error {
            Logger.error(message: error.localizedDescription)
        }
    }
}

extension NearbyManager: MCNearbyServiceAdvertiserDelegate {
    func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void
    ) {
        var dict: Dictionary<String, String>?
        do {
            dict = try JSONSerialization.jsonObject(with: context ?? Data()) as? Dictionary<String, String>
        } catch let error {
            Logger.error(message: error.localizedDescription)
        }
        _ = NearbyDevicesStore.instance.add(for: peerID, discoveryInfo: dict)
        self.invitationHandlers[peerID.displayName] = invitationHandler
        
    }
    
}

extension NearbyManager: MCNearbyServiceBrowserDelegate {
    func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) {
        _ = NearbyDevicesStore.instance.add(for: peerID, discoveryInfo: info)
    }
    
    func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
        let device = NearbyDevicesStore.instance.find(for: peerID.displayName)
        device?.deleteSession()
        NearbyDevicesStore.instance.remove(for: peerID)
    }
}
