import Combine
import Foundation
import MultipeerConnectivity

final class PeerController: NSObject, ObservableObject {
    // ponytail: Multipeer is the smallest encrypted, zero-config LAN link; move to Network.framework when this deployment target no longer supports it.
    private static let serviceType = "anime25d"

    @Published private(set) var connectionLabel = "Standalone"
    var onCommand: ((RigCommand) -> Void)?
    var onStatus: ((String) -> Void)?

    private let peerID: MCPeerID
    private let session: MCSession
    private var advertiser: MCNearbyServiceAdvertiser?
    private var browser: MCNearbyServiceBrowser?

    override init() {
        let id = MCPeerID(displayName: "Anime25D-\(UUID().uuidString.prefix(6))")
        peerID = id
        session = MCSession(peer: id, securityIdentity: nil, encryptionPreference: .required)
        super.init()
        session.delegate = self

        #if os(macOS)
        let advertiser = MCNearbyServiceAdvertiser(peer: id, discoveryInfo: ["role": "mac"], serviceType: Self.serviceType)
        advertiser.delegate = self
        advertiser.startAdvertisingPeer()
        self.advertiser = advertiser
        #else
        let browser = MCNearbyServiceBrowser(peer: id, serviceType: Self.serviceType)
        browser.delegate = self
        browser.startBrowsingForPeers()
        self.browser = browser
        #endif
    }

    deinit {
        advertiser?.stopAdvertisingPeer()
        browser?.stopBrowsingForPeers()
        session.disconnect()
    }

    func send(_ command: RigCommand) {
        guard command.isValid, !session.connectedPeers.isEmpty,
              let data = try? JSONEncoder().encode(command) else { return }
        try? session.send(data, toPeers: session.connectedPeers, with: .reliable)
    }

    private func refreshStatus() {
        let peers = session.connectedPeers
        let label = peers.isEmpty ? "Standalone" : "Connected: \(peers.map(\.displayName).joined(separator: ", "))"
        DispatchQueue.main.async { [weak self] in
            self?.connectionLabel = label
            self?.onStatus?(label)
        }
    }
}

extension PeerController: MCSessionDelegate {
    func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
        refreshStatus()
    }

    func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
        guard let command = try? JSONDecoder().decode(RigCommand.self, from: data), command.isValid else { return }
        DispatchQueue.main.async { [weak self] in self?.onCommand?(command) }
    }

    func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {}

    func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {}

    func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {}
}

extension PeerController: MCNearbyServiceAdvertiserDelegate {
    func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
        invitationHandler(session.connectedPeers.isEmpty, session)
    }
}

extension PeerController: MCNearbyServiceBrowserDelegate {
    func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String: String]?) {
        guard session.connectedPeers.isEmpty, info?["role"] == "mac" else { return }
        browser.invitePeer(peerID, to: session, withContext: nil, timeout: 15)
    }

    func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {}
}
