package com.bitchat.android.hotspot

import android.Manifest
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.wifi.p2p.WifiP2pConfig
import android.net.wifi.p2p.WifiP2pGroup
import android.net.wifi.p2p.WifiP2pManager
import android.net.wifi.p2p.WifiP2pManager.*
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.util.Log
import java.net.NetworkInterface
import java.security.SecureRandom
import kotlin.random.Random

/**
 * Manages Wi-Fi P2P (Wi-Fi Direct) hotspot for offline APK sharing.
 * Based on Briar's implementation.
 */
class HotspotManager(private val context: Context) {

    companion object {
        private const val TAG = "HotspotMgr"

        // Retry configuration
        private const val MAX_FRAMEWORK_ATTEMPTS = 5
        private const val RETRY_DELAY_MILLIS = 1000L

        // Group info polling interval
        private const val GROUP_INFO_POLL_INTERVAL_MILLIS = 1000L

        // Give up if the group never forms within this window after creation succeeded
        private const val GROUP_FORMATION_TIMEOUT_MILLIS = 15_000L

        // SSID and password configuration
        private const val SSID_PREFIX = "DIRECT-BC-" // BC for BitChat
        private const val SSID_SUFFIX_LENGTH = 8
        private const val PASSWORD_LENGTH = 16

        // Characters to use for random generation (excluding confusing ones)
        private const val RANDOM_CHARS = "ABCDEFGHJKLMNPQRTUVWXY34679" // No 0,O,5,S,1,l,I
    }

    private val wifiP2pManager: WifiP2pManager? =
        context.getSystemService(Context.WIFI_P2P_SERVICE) as? WifiP2pManager

    private var channel: Channel? = null
    private var wakeLock: PowerManager.WakeLock? = null
    private var wifiLock: android.net.wifi.WifiManager.WifiLock? = null

    private val handler = Handler(Looper.getMainLooper())
    private val random = SecureRandom()

    private var currentGroup: WifiP2pGroup? = null
    private var callback: HotspotCallback? = null
    private var isStarting = false
    private var hasNotifiedStarted = false // Track if we've notified the callback
    private var isReceiverRegistered = false // Track receiver registration to prevent leaks

    // Saved credentials for reconnection
    private var savedSsid: String? = null
    private var savedPassword: String? = null

    // Broadcast receiver for Wi-Fi P2P events
    private val broadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            when (intent.action) {
                WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
                    val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)
                    Log.d(TAG, "Wi-Fi P2P state changed: $state")
                }
                WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
                    Log.d(TAG, "Wi-Fi P2P connection changed")
                    requestGroupInfo()
                }
            }
        }
    }

    /**
     * Start the Wi-Fi P2P hotspot.
     */
    fun startHotspot(callback: HotspotCallback) {
        if (isStarting) {
            Log.w(TAG, "Hotspot already starting")
            return
        }

        if (wifiP2pManager == null) {
            Log.e(TAG, "Wi-Fi P2P not available on this device")
            callback.onError("Wi-Fi Direct not supported on this device")
            return
        }

        val missingPermissions = HotspotPermissions.missingFrom(context)
        if (missingPermissions.isNotEmpty()) {
            Log.w(TAG, "Cannot start hotspot; missing required permissions: $missingPermissions")
            val message = if (Manifest.permission.ACCESS_LOCAL_NETWORK in missingPermissions) {
                "Local network permission is required to share the app over the hotspot"
            } else {
                "Nearby Wi-Fi permission is required to start the hotspot"
            }
            callback.onError(message)
            return
        }

        this.callback = callback
        isStarting = true

        Log.d(TAG, "Starting Wi-Fi P2P hotspot")

        // Register broadcast receiver (only if not already registered)
        if (!isReceiverRegistered) {
            val intentFilter = IntentFilter().apply {
                addAction(WIFI_P2P_STATE_CHANGED_ACTION)
                addAction(WIFI_P2P_CONNECTION_CHANGED_ACTION)
            }
            context.registerReceiver(broadcastReceiver, intentFilter)
            isReceiverRegistered = true
            Log.d(TAG, "Broadcast receiver registered")
        }

        // Acquire locks
        acquireLocks()

        // Load or generate credentials
        if (savedSsid == null || savedPassword == null) {
            savedSsid = generateSsid()
            savedPassword = generatePassword()
            Log.d(TAG, "Generated new credentials: SSID=$savedSsid")
        } else {
            Log.d(TAG, "Using saved credentials: SSID=$savedSsid")
        }

        // Start P2P framework with retries
        startWifiP2pFramework(1)
    }

    /**
     * Stop the hotspot.
     */
    fun stopHotspot() {
        Log.d(TAG, "Stopping hotspot")

        isStarting = false
        hasNotifiedStarted = false

        // Stop group info polling
        handler.removeCallbacksAndMessages(null)

        // Remove group
        channel?.let { ch ->
            wifiP2pManager?.removeGroup(ch, object : ActionListener {
                override fun onSuccess() {
                    Log.d(TAG, "Group removed successfully")
                }
                override fun onFailure(reason: Int) {
                    Log.w(TAG, "Failed to remove group: $reason")
                }
            })
        }

        // Release locks
        releaseLocks()

        // Unregister receiver (only if registered)
        if (isReceiverRegistered) {
            try {
                context.unregisterReceiver(broadcastReceiver)
                isReceiverRegistered = false
                Log.d(TAG, "Broadcast receiver unregistered")
            } catch (e: IllegalArgumentException) {
                Log.w(TAG, "Receiver was not registered", e)
                isReceiverRegistered = false
            }
        }

        currentGroup = null
        channel = null
        callback = null
    }

    /**
     * Get current connection information.
     */
    fun getConnectionInfo(): ConnectionInfo? {
        val group = currentGroup ?: return null
        val ipAddress = getAccessPointAddress()

        return ConnectionInfo(
            ssid = group.networkName ?: savedSsid ?: "",
            password = group.passphrase ?: savedPassword ?: "",
            ipAddress = ipAddress ?: "192.168.49.1", // Fallback to standard P2P IP
            connectedPeers = group.clientList?.size ?: 0
        )
    }

    /**
     * Start Wi-Fi P2P framework with retry logic.
     */
    private fun startWifiP2pFramework(attempt: Int) {
        if (attempt > MAX_FRAMEWORK_ATTEMPTS) {
            Log.e(TAG, "Failed to start P2P framework after $MAX_FRAMEWORK_ATTEMPTS attempts")
            failStartup("Failed to start hotspot. Please try again.")
            return
        }

        Log.d(TAG, "Starting P2P framework (attempt $attempt/$MAX_FRAMEWORK_ATTEMPTS)")

        channel = wifiP2pManager?.initialize(context, Looper.getMainLooper(), null)

        if (channel == null) {
            Log.e(TAG, "Failed to initialize P2P channel")
            handler.postDelayed({
                startWifiP2pFramework(attempt + 1)
            }, RETRY_DELAY_MILLIS)
            return
        }

        createGroup(attempt)
    }

    /**
     * Create Wi-Fi P2P group.
     */
    @SuppressLint("MissingPermission")
    private fun createGroup(attempt: Int) {
        val ch = channel ?: return

        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                // Android 10+: Custom SSID and password
                val config = WifiP2pConfig.Builder()
                    .setNetworkName(savedSsid!!)
                    .setPassphrase(savedPassword!!)
                    .setGroupOperatingBand(WifiP2pConfig.GROUP_OWNER_BAND_2GHZ) // Force 2.4GHz for compatibility
                    .build()

                wifiP2pManager?.createGroup(ch, config, groupActionListener(attempt, ch))
            } else {
                // Android 9 and below: System-generated SSID/password
                wifiP2pManager?.createGroup(ch, groupActionListener(attempt, ch))
            }
        } catch (e: SecurityException) {
            Log.e(TAG, "Wi-Fi permission was revoked while creating the group", e)
            failStartup("A required Wi-Fi or local network permission was revoked. Grant it and try again.")
        }
    }

    private fun groupActionListener(attempt: Int, requestChannel: Channel) = object : ActionListener {
        override fun onSuccess() {
            if (channel !== requestChannel) {
                Log.w(TAG, "Removing group created after hotspot was stopped")
                wifiP2pManager?.removeGroup(requestChannel, null)
                return
            }
            Log.d(TAG, "P2P group created successfully")
            isStarting = false
            // Don't call onHotspotStarted() yet - wait for group info
            startGroupInfoPolling()
        }

        override fun onFailure(reason: Int) {
            if (channel != null) {
                handleGroupCreationFailure(reason, attempt)
            }
        }
    }

    /**
     * Handle group creation failure with retry logic.
     */
    private fun handleGroupCreationFailure(reason: Int, attempt: Int) {
        val reasonStr = when (reason) {
            ERROR -> "ERROR"
            P2P_UNSUPPORTED -> "P2P_UNSUPPORTED"
            BUSY -> "BUSY"
            else -> "UNKNOWN($reason)"
        }

        Log.w(TAG, "Failed to create group: $reasonStr")

        if (reason == BUSY && attempt < MAX_FRAMEWORK_ATTEMPTS) {
            // Framework is busy, retry
            Log.d(TAG, "P2P framework busy, retrying...")
            handler.postDelayed({
                startWifiP2pFramework(attempt + 1)
            }, RETRY_DELAY_MILLIS)
        } else {
            failStartup("Failed to create hotspot: $reasonStr")
        }
    }

    /**
     * Terminal startup failure: release all resources (locks, receiver, handler
     * callbacks) before notifying the callback, so a failed attempt doesn't leak
     * and block subsequent attempts.
     */
    private fun failStartup(message: String) {
        val cb = callback
        stopHotspot()
        cb?.onError(message)
    }

    /**
     * Start polling for group info to track connected clients.
     */
    private fun startGroupInfoPolling() {
        requestGroupInfo()

        // Keep polling even while the group info is still null — the first
        // requestGroupInfo() after createGroup() can legitimately return null
        // while the group is forming. Give up only after a timeout.
        var elapsedMillis = 0L
        handler.postDelayed(object : Runnable {
            override fun run() {
                if (channel == null) return

                elapsedMillis += GROUP_INFO_POLL_INTERVAL_MILLIS
                if (currentGroup == null && !hasNotifiedStarted &&
                    elapsedMillis >= GROUP_FORMATION_TIMEOUT_MILLIS
                ) {
                    Log.e(TAG, "Group never formed within ${GROUP_FORMATION_TIMEOUT_MILLIS}ms")
                    failStartup("Hotspot failed to start. Please try again.")
                    return
                }

                requestGroupInfo()
                handler.postDelayed(this, GROUP_INFO_POLL_INTERVAL_MILLIS)
            }
        }, GROUP_INFO_POLL_INTERVAL_MILLIS)
    }

    /**
     * Request current group information.
     */
    @SuppressLint("MissingPermission")
    private fun requestGroupInfo() {
        val ch = channel ?: return

        try {
            wifiP2pManager?.requestGroupInfo(ch) { group ->
                if (group != null) {
                    currentGroup = group

                    // Update saved credentials if using system-generated ones
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
                        savedSsid = group.networkName
                        savedPassword = group.passphrase
                    }

                    // Notify callback on FIRST successful group info retrieval
                    if (!hasNotifiedStarted) {
                        hasNotifiedStarted = true
                        Log.d(TAG, "Group info received, notifying callback")
                        callback?.onHotspotStarted()
                    } else {
                        // Subsequent updates
                        callback?.onConnectionInfoUpdated(getConnectionInfo())
                    }
                } else {
                    Log.w(TAG, "requestGroupInfo returned null group")
                }
            }
        } catch (e: SecurityException) {
            Log.e(TAG, "Wi-Fi permission was revoked while reading group info", e)
            failStartup("A required Wi-Fi or local network permission was revoked. Grant it and try again.")
        }
    }

    /**
     * Acquire WakeLock and WifiLock to keep hotspot active.
     */
    private fun acquireLocks() {
        try {
            val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
            wakeLock = powerManager.newWakeLock(
                PowerManager.PARTIAL_WAKE_LOCK,
                "BitChat:HotspotWakeLock"
            )
            wakeLock?.acquire(30 * 60 * 1000L)

            val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as android.net.wifi.WifiManager
            val lockType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF
            } else {
                android.net.wifi.WifiManager.WIFI_MODE_FULL
            }
            wifiLock = wifiManager.createWifiLock(lockType, "BitChat:HotspotWifiLock")
            wifiLock?.acquire()

            Log.d(TAG, "Acquired WakeLock and WifiLock")
        } catch (e: Exception) {
            Log.e(TAG, "Error acquiring locks", e)
        }
    }

    /**
     * Release WakeLock and WifiLock.
     */
    private fun releaseLocks() {
        try {
            wakeLock?.let {
                if (it.isHeld) {
                    it.release()
                }
            }
            wakeLock = null

            wifiLock?.let {
                if (it.isHeld) {
                    it.release()
                }
            }
            wifiLock = null

            Log.d(TAG, "Released WakeLock and WifiLock")
        } catch (e: Exception) {
            Log.e(TAG, "Error releasing locks", e)
        }
    }

    /**
     * Get the IP address of the P2P access point.
     * Looks for network interface starting with "p2p".
     */
    private fun getAccessPointAddress(): String? {
        try {
            val interfaces = NetworkInterface.getNetworkInterfaces()
            while (interfaces.hasMoreElements()) {
                val iface = interfaces.nextElement()
                if (iface.name.startsWith("p2p")) {
                    val addresses = iface.interfaceAddresses
                    for (addr in addresses) {
                        val address = addr.address
                        // IPv4 only (4 bytes)
                        if (address.address.size == 4) {
                            return address.hostAddress
                        }
                    }
                }
            }
        } catch (e: Exception) {
            Log.e(TAG, "Error getting access point address", e)
        }
        return null
    }

    /**
     * Generate random SSID.
     * Format: DIRECT-BC-XXXXXXXX
     */
    private fun generateSsid(): String {
        val suffix = (1..SSID_SUFFIX_LENGTH)
            .map { RANDOM_CHARS[random.nextInt(RANDOM_CHARS.length)] }
            .joinToString("")
        return "$SSID_PREFIX$suffix"
    }

    /**
     * Generate random password.
     * 16 characters, excluding confusing characters.
     */
    private fun generatePassword(): String {
        return (1..PASSWORD_LENGTH)
            .map { RANDOM_CHARS[random.nextInt(RANDOM_CHARS.length)] }
            .joinToString("")
    }

    /**
     * Connection information for the hotspot.
     */
    data class ConnectionInfo(
        val ssid: String,
        val password: String,
        val ipAddress: String,
        val connectedPeers: Int
    )

    /**
     * Callback interface for hotspot events.
     */
    interface HotspotCallback {
        fun onHotspotStarted()
        fun onConnectionInfoUpdated(info: ConnectionInfo?)
        fun onError(message: String)
    }
}
