package hanson.xyz.vpnhotspotmod

import android.os.Build
import hanson.xyz.vpnhotspotmod.App.Companion.app
import hanson.xyz.vpnhotspotmod.net.Routing
import hanson.xyz.vpnhotspotmod.net.TetherType
import hanson.xyz.vpnhotspotmod.net.wifi.WifiDoubleLock
import hanson.xyz.vpnhotspotmod.widget.SmartSnackbar
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.runBlocking
import timber.log.Timber

abstract class RoutingManager(private val caller: Any, val downstream: String, private val forceWifi: Boolean = false) {
    companion object {
        private const val KEY_MASQUERADE_MODE = "service.masqueradeMode"
        var masqueradeMode: Routing.MasqueradeMode
            get() = app.pref.run {
                getString(KEY_MASQUERADE_MODE, null)?.let { return@run Routing.MasqueradeMode.valueOf(it) }
                if (getBoolean("service.masquerade", true)) {   // legacy settings
                    Routing.MasqueradeMode.Simple
                } else Routing.MasqueradeMode.None
            }
            set(value) = app.pref.edit().putString(KEY_MASQUERADE_MODE, value.name).apply()

        /**
         * Thread safety: needs protection by companion object!
         */
        private val active = mutableMapOf<String, RoutingManager>()

        fun clean(reinit: Boolean = true) = synchronized(this) {
            if (!reinit && active.isEmpty()) return@synchronized
            for (manager in active.values) manager.routing?.stop()
            try {
                runBlocking { Routing.clean() }
            } catch (e: Exception) {
                Timber.d(e)
                SmartSnackbar.make(e).show()
                return
            }
            if (reinit) for (manager in active.values) manager.initRoutingLocked()
        }
    }

    /**
     * Both repeater and local-only hotspot are Wi-Fi based.
     */
    class LocalOnly(caller: Any, downstream: String) : RoutingManager(caller, downstream, true) {
        override fun Routing.configure() {
            ipForward() // local only interfaces need to enable ip_forward
            forward()
            masquerade(masqueradeMode)
            commit()
        }
    }

    var started = false
        private set
    /**
     * Thread safety: needs protection by companion object!
     */
    private var routing: Routing? = null
    private var isWifi = forceWifi || TetherType.ofInterface(downstream).isWifi

    fun start(fromMonitor: Boolean = false) = synchronized(RoutingManager) {
        started = true
        when (val other = active.putIfAbsent(downstream, this)) {
            null -> {
                if (isWifi) WifiDoubleLock.acquire(this)
                if (!forceWifi && Build.VERSION.SDK_INT >= 30) TetherType.listener[this] = {
                    val isWifiNow = TetherType.ofInterface(downstream).isWifi
                    if (isWifi != isWifiNow) {
                        if (isWifi) WifiDoubleLock.release(this) else WifiDoubleLock.acquire(this)
                        isWifi = isWifiNow
                    }
                }
                initRoutingLocked(fromMonitor)
            }
            this -> true    // already started
            else -> {
                val msg = "Double routing detected for $downstream from $caller != ${other.caller}"
                Timber.w(RuntimeException(msg))
                SmartSnackbar.make(msg).show()
                false
            }
        }
    }

    private fun initRoutingLocked(fromMonitor: Boolean = false) = try {
        routing = Routing(caller, downstream).apply {
            try {
                configure()
            } catch (e: Exception) {
                revert()
                throw e
            }
        }
        true
    } catch (e: Exception) {
        when (e) {
            is Routing.InterfaceNotFoundException -> if (!fromMonitor) Timber.d(e)
            !is CancellationException -> Timber.w(e)
        }
        if (e !is Routing.InterfaceNotFoundException || !fromMonitor) SmartSnackbar.make(e).show()
        routing = null
        false
    }

    protected abstract fun Routing.configure()

    fun stop() = synchronized(RoutingManager) {
        started = false
        if (active.remove(downstream, this)) {
            if (!forceWifi && Build.VERSION.SDK_INT >= 30) TetherType.listener -= this
            if (isWifi) WifiDoubleLock.release(this)
            routing?.revert()
        }
    }
}
