package de.duffy.screencast.connect

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.WifiP2pDevice
import android.net.wifi.p2p.WifiP2pInfo
import android.net.wifi.p2p.WifiP2pManager
import android.os.Build
import android.os.Looper
import android.util.Log
import android.net.wifi.WpsInfo
import java.net.InetAddress

/**
 * Wi-Fi Direct: findet Fernseher, die im Screen-Mirroring-Wartemodus stehen,
 * und baut die Direktverbindung zu ihnen auf.
 *
 * Das laeuft komplett am WLAN vorbei - wichtig in Hotels und Gaestenetzen, wo
 * Geraete sich gegenseitig ohnehin nicht sehen duerfen.
 */
class WifiDirectController(private val context: Context) {

    data class Peer(
        val name: String,
        val address: String,
        /** Meldet sich das Geraet als Miracast-Empfaenger? */
        val isDisplaySink: Boolean,
        /** Wartet es gerade auf eine Verbindung? */
        val sessionAvailable: Boolean,
        val rtspPort: Int,
        val status: Int
    )

    private val manager: WifiP2pManager? =
        context.getSystemService(Context.WIFI_P2P_SERVICE) as? WifiP2pManager
    private var channel: WifiP2pManager.Channel? = null
    private var receiver: BroadcastReceiver? = null

    var onPeersChanged: ((List<Peer>) -> Unit)? = null
    var onConnected: ((InetAddress, Boolean) -> Unit)? = null
    var onLog: ((String) -> Unit)? = null

    val isSupported: Boolean get() = manager != null

    fun start(): Boolean {
        val mgr = manager ?: return false
        if (channel != null) return true
        channel = mgr.initialize(context, Looper.getMainLooper(), null)
        if (channel == null) return false

        val filter = IntentFilter().apply {
            addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)
            addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
            addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
            addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
        }
        receiver = object : BroadcastReceiver() {
            override fun onReceive(ctx: Context?, intent: Intent?) = handle(intent)
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED)
        } else {
            @Suppress("UnspecifiedRegisterReceiverFlag") context.registerReceiver(receiver, filter)
        }
        return true
    }

    fun stop() {
        receiver?.let { try { context.unregisterReceiver(it) } catch (_: Exception) {} }
        receiver = null
        try { manager?.stopPeerDiscovery(channel, null) } catch (_: Exception) {}
        channel = null
    }

    @SuppressLint("MissingPermission")
    fun discover() {
        val mgr = manager ?: return
        val ch = channel ?: return
        try {
            mgr.discoverPeers(ch, object : WifiP2pManager.ActionListener {
                override fun onSuccess() { log("Suche laeuft ...") }
                override fun onFailure(reason: Int) { log("Suche fehlgeschlagen (${reasonText(reason)})") }
            })
        } catch (e: SecurityException) {
            log("Berechtigung fuer die Geraetesuche fehlt")
        }
    }

    @SuppressLint("MissingPermission")
    fun connect(peer: Peer) {
        val mgr = manager ?: return
        val ch = channel ?: return
        val config = WifiP2pConfig().apply {
            deviceAddress = peer.address
            wps.setup = WpsInfo.PBC
            // Der Fernseher soll Gruppenbesitzer werden - dann kennen wir
            // gleich seine Adresse und koennen die Sitzung dorthin aufbauen.
            groupOwnerIntent = 0
        }
        try {
            mgr.connect(ch, config, object : WifiP2pManager.ActionListener {
                override fun onSuccess() { log("Verbindungsanfrage an ${peer.name} gesendet") }
                override fun onFailure(reason: Int) { log("Verbindung abgelehnt (${reasonText(reason)})") }
            })
        } catch (e: SecurityException) {
            log("Berechtigung fuer den Verbindungsaufbau fehlt")
        }
    }

    @SuppressLint("MissingPermission")
    fun disconnect() {
        val mgr = manager ?: return
        val ch = channel ?: return
        try { mgr.removeGroup(ch, null) } catch (_: Exception) {}
    }

    @SuppressLint("MissingPermission")
    private fun handle(intent: Intent?) {
        val mgr = manager ?: return
        val ch = channel ?: return
        when (intent?.action) {
            WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
                try {
                    mgr.requestPeers(ch) { list ->
                        onPeersChanged?.invoke(list.deviceList.map { it.toPeer() })
                    }
                } catch (_: SecurityException) {}
            }
            WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
                mgr.requestConnectionInfo(ch) { info: WifiP2pInfo ->
                    if (info.groupFormed) {
                        val target = info.groupOwnerAddress
                        log("Verbunden. Gruppenbesitzer: ${target?.hostAddress}, wir sind " +
                            if (info.isGroupOwner) "Besitzer" else "Gast")
                        if (target != null) onConnected?.invoke(target, info.isGroupOwner)
                    }
                }
            }
            WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
                val enabled = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1) ==
                    WifiP2pManager.WIFI_P2P_STATE_ENABLED
                if (!enabled) log("Wi-Fi Direct ist ausgeschaltet - bitte WLAN einschalten")
            }
        }
    }

    private fun WifiP2pDevice.toPeer(): Peer {
        var sink = false
        var available = false
        var port = 7236
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            try {
                val wfd = wfdInfo
                if (wfd != null) {
                    available = wfd.isSessionAvailable
                    port = wfd.controlPort.takeIf { it > 0 } ?: 7236
                    sink = true
                }
            } catch (_: Throwable) { /* nicht jedes System liefert die Info */ }
        }
        // Fallback ueber den Geraetetyp: "7-0050F204-1" ist ein Display.
        if (!sink) sink = primaryDeviceType?.startsWith("7-") == true
        return Peer(
            name = deviceName ?: "Unbenannt",
            address = deviceAddress,
            isDisplaySink = sink,
            sessionAvailable = available,
            rtspPort = port,
            status = status
        )
    }

    private fun log(message: String) {
        Log.i(TAG, message)
        onLog?.invoke(message)
    }

    private fun reasonText(reason: Int): String = when (reason) {
        WifiP2pManager.P2P_UNSUPPORTED -> "Gerät unterstützt kein Wi-Fi Direct"
        WifiP2pManager.BUSY -> "System gerade beschäftigt"
        WifiP2pManager.ERROR -> "interner Fehler"
        else -> "Code $reason"
    }

    companion object { private const val TAG = "WifiDirect" }
}
