import BitFoundation
import BitLogger
import Foundation

/// Narrow environment for `BLEPublicMessageHandler`.
///
/// All queue hops (collections registry reads, BLE-queue link-state reads,
/// main-actor UI notification) live inside the closures supplied by
/// `BLEService`, keeping the handler queue-agnostic and synchronously testable.
struct BLEPublicMessageHandlerEnvironment {
    /// Local peer identity at the time the message is handled.
    let localPeerID: () -> PeerID
    /// Local nickname used for sender resolution and collision checks.
    let localNickname: () -> String
    /// Current time source.
    let now: () -> Date
    /// Snapshot of known peers keyed by ID (registry read).
    let peersSnapshot: () -> [PeerID: BLEPeerInfo]
    /// Verifies a packet's signature against a known signing public key.
    let verifyPacketSignature: (_ packet: BitchatPacket, _ signingPublicKey: Data) -> Bool
    /// Resolves a display name from a verified packet signature for peers missing from the registry.
    let signedSenderDisplayName: (_ packet: BitchatPacket, _ peerID: PeerID) -> String?
    /// Tracks the broadcast message packet for gossip sync.
    let trackPacketSeen: (BitchatPacket) -> Void
    /// Direct link state for the peer (BLE-queue read).
    let linkState: (PeerID) -> (hasPeripheral: Bool, hasCentral: Bool)
    /// Resolves and consumes the original message ID for our own re-broadcast.
    let takeSelfBroadcastMessageID: (BitchatPacket) -> String?
    /// Delivers `.publicMessageReceived` to the UI as one main-actor hop.
    let deliverPublicMessage: (
        _ peerID: PeerID,
        _ nickname: String,
        _ content: String,
        _ timestamp: Date,
        _ messageID: String?
    ) -> Void
}

/// Orchestrates inbound public (broadcast) messages: freshness/self-echo
/// policy, sender display-name resolution, gossip tracking, payload decoding,
/// and UI delivery.
final class BLEPublicMessageHandler {
    private let environment: BLEPublicMessageHandlerEnvironment

    init(environment: BLEPublicMessageHandlerEnvironment) {
        self.environment = environment
    }

    func handle(_ packet: BitchatPacket, from peerID: PeerID) {
        let env = environment
        let now = env.now()
        let messageDecision = BLEPublicMessagePolicy.evaluate(
            packet: packet,
            from: peerID,
            localPeerID: env.localPeerID(),
            now: now
        )

        let messagePolicy: BLEPublicMessageAcceptance
        switch messageDecision {
        case .accept(let acceptance):
            messagePolicy = acceptance
        case .reject(.selfEcho):
            return
        case .reject(.staleBroadcast(let ageSeconds)):
            SecureLogger.debug("⏰ Ignoring stale broadcast message from \(peerID.id.prefix(8))… (age: \(ageSeconds)s)", category: .session)
            return
        }

        // Snapshot peers to avoid concurrent mutation while iterating during nickname collision checks.
        let peersSnapshot = env.peersSnapshot()

        // Public messages are always signed by their sender. `senderID` is
        // attacker-controlled, so registry membership alone is NOT proof of
        // identity — a peer in the registry as "verified" could be impersonated
        // by anyone spoofing their senderID. Require a valid packet signature
        // from the claimed sender (our own echoes are exempt; they are matched
        // by self-broadcast tracking below).
        //
        // Verify against the signing key already in the (synchronously-updated)
        // peer registry first: identity-cache persistence is asynchronous, so a
        // message arriving right after a verified announce would otherwise be
        // dropped because `signedSenderDisplayName` only searches the persisted
        // cache. Fall back to that persisted-identity lookup for peers not (yet)
        // in the registry.
        let isSelf = peerID == env.localPeerID()
        let registrySigningKey = peersSnapshot[peerID]?.signingPublicKey
        let verifiedViaRegistry = !isSelf
            && (registrySigningKey.map { env.verifyPacketSignature(packet, $0) } ?? false)
        let signedDisplayName = (isSelf || verifiedViaRegistry) ? nil : env.signedSenderDisplayName(packet, peerID)
        guard isSelf || verifiedViaRegistry || signedDisplayName != nil else {
            SecureLogger.warning("🚫 Dropping public message with missing/invalid signature for claimed sender \(peerID.id.prefix(8))…", category: .security)
            return
        }

        // Authenticity is established; prefer the registry's collision-resolved
        // display name, then the signature-derived name.
        guard let senderNickname = BLEPeerSenderDisplayName.resolveKnownPeer(
            peerID: peerID,
            localPeerID: env.localPeerID(),
            localNickname: env.localNickname(),
            peers: peersSnapshot,
            allowConnectedUnverified: false
        ) ?? signedDisplayName else {
            SecureLogger.warning("🚫 Dropping public message from unknown peer \(peerID.id.prefix(8))…", category: .security)
            return
        }

        if messagePolicy.shouldTrackForSync {
            env.trackPacketSeen(packet)
        }

        guard let content = String(data: packet.payload, encoding: .utf8) else {
            SecureLogger.error("❌ Failed to decode message payload as UTF-8", category: .session)
            return
        }
        // Determine if we have a direct link to the sender
        let directLink = env.linkState(peerID)
        let hasDirectLink = directLink.hasPeripheral || directLink.hasCentral

        let pathTag = hasDirectLink ? "direct" : "mesh"
        SecureLogger.debug("💬 [\(senderNickname)] TTL:\(packet.ttl) (\(pathTag)) chars=\(content.count) bytes=\(packet.payload.count)", category: .session)

        let ts = Date(timeIntervalSince1970: Double(packet.timestamp) / 1000)
        let messageID: String?
        if peerID == env.localPeerID() {
            messageID = env.takeSelfBroadcastMessageID(packet)
        } else {
            // The wire carries no message ID; derive the stable one every
            // device agrees on so bridged copies dedup against the radio copy.
            messageID = MeshMessageIdentity.stableID(
                senderIDHex: peerID.id,
                timestampMs: packet.timestamp,
                content: content
            )
        }
        env.deliverPublicMessage(peerID, senderNickname, content, ts, messageID)
    }
}
