package com.bitchat.android.mesh

import android.util.Log
import kotlinx.coroutines.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList

/**
 * Peer information structure with verification status
 * Compatible with iOS PeerInfo structure
 */
data class PeerInfo(
    val id: String,
    var nickname: String,
    var isConnected: Boolean,
    var isDirectConnection: Boolean,
    var noisePublicKey: ByteArray?,
    var signingPublicKey: ByteArray?,      // NEW: Ed25519 public key for verification
    var isVerifiedNickname: Boolean,       // NEW: Verification status flag
    var lastSeen: Long  // Using Long instead of Date for simplicity
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
        
        other as PeerInfo
        
        if (id != other.id) return false
        if (nickname != other.nickname) return false
        if (isConnected != other.isConnected) return false
        if (isDirectConnection != other.isDirectConnection) return false
        if (noisePublicKey != null) {
            if (other.noisePublicKey == null) return false
            if (!noisePublicKey.contentEquals(other.noisePublicKey)) return false
        } else if (other.noisePublicKey != null) return false
        if (signingPublicKey != null) {
            if (other.signingPublicKey == null) return false
            if (!signingPublicKey.contentEquals(other.signingPublicKey)) return false
        } else if (other.signingPublicKey != null) return false
        if (isVerifiedNickname != other.isVerifiedNickname) return false
        if (lastSeen != other.lastSeen) return false
        
        return true
    }
    
    override fun hashCode(): Int {
        var result = id.hashCode()
        result = 31 * result + nickname.hashCode()
        result = 31 * result + isConnected.hashCode()
        result = 31 * result + isDirectConnection.hashCode()
        result = 31 * result + (noisePublicKey?.contentHashCode() ?: 0)
        result = 31 * result + (signingPublicKey?.contentHashCode() ?: 0)
        result = 31 * result + isVerifiedNickname.hashCode()
        result = 31 * result + lastSeen.hashCode()
        return result
    }
}

/**
 * Manages active peers, nicknames, RSSI tracking, and peer fingerprints
 * Extracted from BluetoothMeshService for better separation of concerns
 * 
 * Now includes centralized peer fingerprint management via PeerFingerprintManager singleton
 * and support for signed announcement verification
 */
class PeerManager {
    
    companion object {
        private const val TAG = "PeerManager"
    }

    // Centralized timeout from AppConstants
    private val stalePeerTimeoutMs: Long = com.bitchat.android.util.AppConstants.Mesh.STALE_PEER_TIMEOUT_MS
    
    // Peer tracking data - enhanced with verification status
    private val peers = ConcurrentHashMap<String, PeerInfo>() // peerID -> PeerInfo
    private val peerRSSI = ConcurrentHashMap<String, Int>()
    private val announcedPeers = CopyOnWriteArrayList<String>()
    private val announcedToPeers = CopyOnWriteArrayList<String>()
    
    // Legacy fields removed: use PeerInfo map exclusively
    
    // Centralized fingerprint management
    private val fingerprintManager = PeerFingerprintManager.getInstance()
    
    // Delegate for callbacks
    var delegate: PeerManagerDelegate? = null
    
    // Callback to check if a peer is directly connected (injected by BluetoothMeshService)
    var isPeerDirectlyConnected: ((String) -> Boolean)? = null

    // Coroutines
    private val managerScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    
    init {
        startPeriodicCleanup()
    }

    // MARK: - New PeerInfo-based methods

    /**
     * Update peer information with verification data
     * Similar to iOS updatePeer method
     */
    fun updatePeerInfo(
        peerID: String,
        nickname: String,
        noisePublicKey: ByteArray,
        signingPublicKey: ByteArray,
        isVerified: Boolean
    ): Boolean {
        if (peerID == "unknown") return false

        fun keysMatch(a: ByteArray?, b: ByteArray?): Boolean {
            if (a == null && b == null) return true
            if (a == null || b == null) return false
            return a.contentEquals(b)
        }
        
        val now = System.currentTimeMillis()
        val existingPeer = peers[peerID]
        val isNewPeer = existingPeer == null
        val wasVerified = existingPeer?.isVerifiedNickname == true
        val nicknameChanged = existingPeer != null && existingPeer.nickname != nickname
        val noiseKeyChanged = existingPeer != null && !keysMatch(existingPeer.noisePublicKey, noisePublicKey)
        val signingKeyChanged = existingPeer != null && !keysMatch(existingPeer.signingPublicKey, signingPublicKey)
        val connectedChanged = existingPeer != null && existingPeer.isConnected != true
        
        // Update or create peer info
        val peerInfo = PeerInfo(
            id = peerID,
            nickname = nickname,
            isConnected = true,
            isDirectConnection = existingPeer?.isDirectConnection ?: false,
            noisePublicKey = noisePublicKey,
            signingPublicKey = signingPublicKey,
            isVerifiedNickname = isVerified,
            lastSeen = now
        )
        
        peers[peerID] = peerInfo
        
        // Update derived state only
        // No legacy maps; peers map is the single source of truth
        // Maintain announcedPeers for first-time announce semantics
        
        val shouldNotify = when {
            isNewPeer && isVerified -> true
            wasVerified != isVerified -> true
            nicknameChanged || noiseKeyChanged || signingKeyChanged || connectedChanged -> true
            else -> false
        }

        if (isNewPeer && isVerified) {
            announcedPeers.add(peerID)
            Log.d(TAG, "🆕 New verified peer: $nickname ($peerID)")
        } else if (isVerified) {
            Log.d(TAG, "🔄 Updated verified peer: $nickname ($peerID)")
        } else {
            Log.d(TAG, "⚠️ Unverified peer announcement from: $nickname ($peerID)")
        }

        if (shouldNotify) {
            notifyPeerListUpdate()
        }
        
        return isNewPeer && isVerified
    }

    /**
     * Get peer info with dynamic direct connection status
     */
    fun getPeerInfo(peerID: String): PeerInfo? {
        return peers[peerID]?.let { info ->
            // Dynamically check direct connection status from ConnectionManager
            val isDirect = isPeerDirectlyConnected?.invoke(peerID) ?: false
            if (info.isDirectConnection != isDirect) {
                info.copy(isDirectConnection = isDirect)
            } else {
                info
            }
        }
    }

    /**
     * Check if peer is verified
     */
    fun isPeerVerified(peerID: String): Boolean {
        return peers[peerID]?.isVerifiedNickname == true
    }

    /**
     * Get all verified peers with dynamic direct connection status
     */
    fun getVerifiedPeers(): Map<String, PeerInfo> {
        return peers.filterValues { it.isVerifiedNickname }.mapValues { (_, info) ->
            val isDirect = isPeerDirectlyConnected?.invoke(info.id) ?: false
            if (info.isDirectConnection != isDirect) info.copy(isDirectConnection = isDirect) else info
        }
    }

    // MARK: - Legacy Methods (maintained for compatibility)

    /**
     * Update peer last seen timestamp
     */
    fun updatePeerLastSeen(peerID: String) {
        if (peerID != "unknown") {
            peers[peerID]?.let { info ->
                peers[peerID] = info.copy(lastSeen = System.currentTimeMillis())
            }
        }
    }
    
    /**
     * Add or update peer with nickname
     * Maintained for compatibility. Uses peers map exclusively now.
     */
    fun addOrUpdatePeer(peerID: String, nickname: String): Boolean {
        if (peerID == "unknown") return false
        
        // Clean up stale peer IDs with the same nickname (exact same logic as iOS)
        val now = System.currentTimeMillis()
        val stalePeerIDs = mutableListOf<String>()
        peers.forEach { (existingPeerID, info) ->
            if (info.nickname == nickname && existingPeerID != peerID) {
                val wasRecentlySeen = (now - info.lastSeen) < 10000
                if (!wasRecentlySeen) {
                    stalePeerIDs.add(existingPeerID)
                }
            }
        }
        
        // Remove stale peer IDs
        stalePeerIDs.forEach { stalePeerID ->
            removePeer(stalePeerID, notifyDelegate = false)
        }
        
        // Check if this is a new peer announcement
        val isFirstAnnounce = !announcedPeers.contains(peerID)
        
        // Update peer data
        val existing = peers[peerID]
        if (existing != null) {
            peers[peerID] = existing.copy(nickname = nickname, lastSeen = now, isConnected = true)
        } else {
            peers[peerID] = PeerInfo(
                id = peerID,
                nickname = nickname,
                isConnected = true,
                isDirectConnection = false,
                noisePublicKey = null,
                signingPublicKey = null,
                isVerifiedNickname = false,
                lastSeen = now
            )
        }
        
        // Handle first announcement
        if (isFirstAnnounce) {
            announcedPeers.add(peerID)
            notifyPeerListUpdate()
            return true
        }
        Log.d(TAG, "Updated peer: $peerID ($nickname)")
        return false
    }
    
    /**
     * Remove peer
     */
    fun removePeer(peerID: String, notifyDelegate: Boolean = true) {
        val removed = peers.remove(peerID)
        peerRSSI.remove(peerID)
        announcedPeers.remove(peerID)
        announcedToPeers.remove(peerID)
        
        // Also remove fingerprint mappings
        fingerprintManager.removePeer(peerID)
        
        if (notifyDelegate && removed != null) {
            // Notify specific removal event then list update
            try { delegate?.onPeerRemoved(peerID) } catch (_: Exception) {}
            notifyPeerListUpdate()
        }
    }
    
    /**
     * Update peer RSSI
     */
    fun updatePeerRSSI(peerID: String, rssi: Int) {
        if (peerID != "unknown") {
            peerRSSI[peerID] = rssi
        }
    }
    
    /**
     * Check if peer has been announced to
     */
    fun hasAnnouncedToPeer(peerID: String): Boolean {
        return announcedToPeers.contains(peerID)
    }
    
    /**
     * Mark peer as announced to
     */
    fun markPeerAsAnnouncedTo(peerID: String) {
        if (!announcedToPeers.contains(peerID)) {
            announcedToPeers.add(peerID)
        }
    }
    
    /**
     * Check if peer is active
     */
    fun isPeerActive(peerID: String): Boolean {
        val info = peers[peerID] ?: return false
        return info.isConnected
    }
    
    /**
     * Get peer nickname
     */
    fun getPeerNickname(peerID: String): String? {
        return peers[peerID]?.nickname
    }
    
    /**
     * Get all peer nicknames
     */
    fun getAllPeerNicknames(): Map<String, String> {
        return peers.mapValues { it.value.nickname }
    }
    
    /**
     * Get all peer RSSI values
     */
    fun getAllPeerRSSI(): Map<String, Int> {
        return peerRSSI.toMap()
    }
    
    /**
     * Get list of active peer IDs
     */
    fun getActivePeerIDs(): List<String> {
        return peers.filterValues { it.isConnected }
            .keys
            .toList()
            .sorted()
    }
    
    /**
     * Get active peer count
     */
    fun getActivePeerCount(): Int {
        return getActivePeerIDs().size
    }
    
    /**
     * Clear all peer data
     */
    fun clearAllPeers() {
        peers.clear()
        peerRSSI.clear()
        announcedPeers.clear()
        announcedToPeers.clear()
        
        // Also clear fingerprint mappings
        fingerprintManager.clearAllFingerprints()
        
        notifyPeerListUpdate()
    }
    
    /**
     * Get debug information
     */
    fun getDebugInfo(addressPeerMap: Map<String, String>? = null): String {
        val now = System.currentTimeMillis()
        val activeIds = getActivePeerIDs().toSet()
        return buildString {
            appendLine("=== Peer Manager Debug Info ===")
            appendLine("Active Peers: ${activeIds.size}")
            peers.forEach { (peerID, storedInfo) ->
                // Use dynamic direct status for debug log accuracy
                val isDirect = isPeerDirectlyConnected?.invoke(peerID) ?: false
                val info = if (storedInfo.isDirectConnection != isDirect) storedInfo.copy(isDirectConnection = isDirect) else storedInfo
                
                val timeSince = (now - info.lastSeen) / 1000
                val rssi = peerRSSI[peerID]?.let { "${it} dBm" } ?: "No RSSI"
                val deviceAddress = addressPeerMap?.entries?.find { it.value == peerID }?.key
                val addressInfo = deviceAddress?.let { " [Device: $it]" } ?: " [Device: Unknown]"
                val status = if (activeIds.contains(peerID)) "ACTIVE" else "INACTIVE"
                val direct = if (info.isDirectConnection) "DIRECT" else "ROUTED"
                appendLine("  - $peerID (${info.nickname})$addressInfo - $status/$direct, last seen ${timeSince}s ago, RSSI: $rssi")
            }
            appendLine("Announced Peers: ${announcedPeers.size}")
            appendLine("Announced To Peers: ${announcedToPeers.size}")
        }
    }
    
    /**
     * Get debug information with device addresses
     */
    fun getDebugInfoWithDeviceAddresses(addressPeerMap: Map<String, String>): String {
        return buildString {
            appendLine("=== Device Address to Peer Mapping ===")
            if (addressPeerMap.isEmpty()) {
                appendLine("No device address mappings available")
            } else {
                addressPeerMap.forEach { (deviceAddress, peerID) ->
                    val nickname = peers[peerID]?.nickname ?: "Unknown"
                    val isActive = isPeerActive(peerID)
                    val status = if (isActive) "ACTIVE" else "INACTIVE"
                    appendLine("  Device: $deviceAddress -> Peer: $peerID ($nickname) [$status]")
                }
            }
            appendLine()
            appendLine(getDebugInfo(addressPeerMap))
        }
    }
    
    /**
     * Notify delegate of peer list updates
     */
    private fun notifyPeerListUpdate() {
        val peerList = getActivePeerIDs()
        delegate?.onPeerListUpdated(peerList)
    }

    fun refreshPeerList() {
        notifyPeerListUpdate()
    }
    
    /**
     * Start periodic cleanup of stale peers
     */
    private fun startPeriodicCleanup() {
        managerScope.launch {
            while (isActive) {
                delay(com.bitchat.android.util.AppConstants.Mesh.PEER_CLEANUP_INTERVAL_MS)
                cleanupStalePeers()
            }
        }
    }
    
    /**
     * Clean up stale peers (same 3-minute threshold as iOS)
     */
    private fun cleanupStalePeers() {
        val now = System.currentTimeMillis()
        
        val peersToRemove = peers.filterValues { (now - it.lastSeen) > stalePeerTimeoutMs }
            .keys
            .toList()
        
        peersToRemove.forEach { peerID ->
            Log.d(TAG, "Removing stale peer: $peerID")
            removePeer(peerID)
        }
        
        if (peersToRemove.isNotEmpty()) {
            Log.d(TAG, "Cleaned up ${peersToRemove.size} stale peers")
        }
    }
    
    // MARK: - Fingerprint Management (Centralized)
    
    /**
     * Store fingerprint for a peer after successful Noise handshake
     * This should only be called when a Noise session is established
     * 
     * @param peerID The peer's ID
     * @param publicKey The peer's static public key from Noise handshake
     */
    fun storeFingerprintForPeer(peerID: String, publicKey: ByteArray): String {
        return fingerprintManager.storeFingerprintForPeer(peerID, publicKey)
    }
    
    /**
     * Update peer ID mapping for peer ID rotation
     * 
     * @param oldPeerID The previous peer ID (nullable)
     * @param newPeerID The new peer ID
     * @param fingerprint The persistent fingerprint
     */
    fun updatePeerIDMapping(oldPeerID: String?, newPeerID: String, fingerprint: String) {
        fingerprintManager.updatePeerIDMapping(oldPeerID, newPeerID, fingerprint)
    }
    
    /**
     * Get fingerprint for a specific peer
     * 
     * @param peerID The peer ID to look up
     * @return The fingerprint if found, null otherwise
     */
    fun getFingerprintForPeer(peerID: String): String? {
        return fingerprintManager.getFingerprintForPeer(peerID)
    }
    
    /**
     * Get current peer ID for a specific fingerprint
     * 
     * @param fingerprint The fingerprint to look up
     * @return The current peer ID if found, null otherwise
     */
    fun getPeerIDForFingerprint(fingerprint: String): String? {
        return fingerprintManager.getPeerIDForFingerprint(fingerprint)
    }
    
    /**
     * Check if we have a fingerprint for a specific peer
     * 
     * @param peerID The peer ID to check
     * @return True if we have a fingerprint for this peer, false otherwise
     */
    fun hasFingerprintForPeer(peerID: String): Boolean {
        return fingerprintManager.hasFingerprintForPeer(peerID)
    }
    
    /**
     * Get all current peer ID to fingerprint mappings
     * 
     * @return Immutable copy of all mappings
     */
    fun getAllPeerFingerprints(): Map<String, String> {
        return fingerprintManager.getAllPeerFingerprints()
    }
    
    /**
     * Clear all fingerprint mappings (used for emergency clear)
     */
    fun clearAllFingerprints() {
        fingerprintManager.clearAllFingerprints()
    }
    
    /**
     * Get fingerprint manager debug info
     */
    fun getFingerprintDebugInfo(): String {
        return fingerprintManager.getDebugInfo()
    }
    
    /**
     * Shutdown the manager
     */
    fun shutdown() {
        managerScope.cancel()
        clearAllPeers()
    }
}

/**
 * Delegate interface for peer manager callbacks
 */
interface PeerManagerDelegate {
    fun onPeerListUpdated(peerIDs: List<String>)
    fun onPeerRemoved(peerID: String)
}
