package hanson.xyz.vpnhotspotmod.manage

import android.annotation.TargetApi
import android.bluetooth.BluetoothManager
import android.content.*
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.withStarted
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import hanson.xyz.vpnhotspotmod.*
import hanson.xyz.vpnhotspotmod.App.Companion.app
import hanson.xyz.vpnhotspotmod.databinding.FragmentTetheringBinding
import hanson.xyz.vpnhotspotmod.net.TetherType
import hanson.xyz.vpnhotspotmod.net.TetheringManager
import hanson.xyz.vpnhotspotmod.net.TetheringManager.localOnlyTetheredIfaces
import hanson.xyz.vpnhotspotmod.net.TetheringManager.tetheredIfaces
import hanson.xyz.vpnhotspotmod.net.monitor.TetherTimeoutMonitor
import hanson.xyz.vpnhotspotmod.net.wifi.SoftApConfigurationCompat
import hanson.xyz.vpnhotspotmod.net.wifi.SoftApConfigurationCompat.Companion.toCompat
import hanson.xyz.vpnhotspotmod.net.wifi.WifiApDialogFragment
import hanson.xyz.vpnhotspotmod.net.wifi.WifiApManager
import hanson.xyz.vpnhotspotmod.root.RootManager
import hanson.xyz.vpnhotspotmod.root.WifiApCommands
import hanson.xyz.vpnhotspotmod.util.*
import hanson.xyz.vpnhotspotmod.widget.SmartSnackbar
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.lang.reflect.InvocationTargetException
import java.net.NetworkInterface
import java.net.SocketException

class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener {
    inner class ManagerAdapter : ListAdapter<Manager, RecyclerView.ViewHolder>(Manager),
        TetheringManager.TetheringEventCallback {
        internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) }
        internal val localOnlyHotspotManager by lazy { LocalOnlyHotspotManager(this@TetheringFragment) }
        internal val bluetoothManager by lazy {
            requireContext().getSystemService<BluetoothManager>()?.adapter?.let {
                TetherManager.Bluetooth(this@TetheringFragment, it)
            }
        }
        private val tetherManagers by lazy {
            listOfNotNull(
                TetherManager.Wifi(this@TetheringFragment),
                TetherManager.Usb(this@TetheringFragment),
                bluetoothManager,
            )
        }
        @get:RequiresApi(30)
        private val ethernetManager by lazy @TargetApi(30) { TetherManager.Ethernet(this@TetheringFragment) }

        var activeIfaces = emptyList<String>()
        var localOnlyIfaces = emptyList<String>()
        var erroredIfaces = emptyList<String>()
        private var listDeferred = CompletableDeferred<List<Manager>>(emptyList())
        fun updateEnabledTypes() {
            this@TetheringFragment.enabledTypes =
                (activeIfaces + localOnlyIfaces).map { TetherType.ofInterface(it) }.toSet()
        }

        val lastErrors = mutableMapOf<String, Int>()
        override fun onError(ifName: String, error: Int) {
            if (error == 0) lastErrors.remove(ifName) else lastErrors[ifName] = error
        }

        suspend fun notifyTetherTypeChanged() {
            updateEnabledTypes()
            val lastList = listDeferred.await()
            var first = lastList.indexOfFirst { it is InterfaceManager }
            withStarted {
                if (first >= 0) {
                    notifyItemRangeChanged(first, lastList.indexOfLast { it is InterfaceManager } - first + 1)
                }
                first = lastList.indexOfLast { it !is TetherManager } + 1
                notifyItemRangeChanged(first, lastList.size - first)
            }
        }

        fun update() {
            val deferred = CompletableDeferred<List<Manager>>()
            listDeferred = deferred
            ifaceLookup = try {
                NetworkInterface.getNetworkInterfaces().asSequence().associateBy { it.name }
            } catch (e: Exception) {
                if (e is SocketException) Timber.d(e) else Timber.w(e)
                emptyMap()
            }

            val list = ArrayList<Manager>()
            if (Services.p2p != null) list.add(repeaterManager)
            list.add(localOnlyHotspotManager)
            val monitoredIfaces = binder?.monitoredIfaces ?: emptyList()
            updateMonitorList(activeIfaces - monitoredIfaces.toSet())
            list.addAll((activeIfaces + monitoredIfaces).toSortedSet()
                    .map { InterfaceManager(this@TetheringFragment, it) })
            list.add(ManageBar)
            list.addAll(tetherManagers)
            tetherManagers.forEach { it.updateErrorMessage(erroredIfaces, lastErrors) }
            if (Build.VERSION.SDK_INT >= 30) {
                list.add(ethernetManager)
                ethernetManager.updateErrorMessage(erroredIfaces, lastErrors)
            }
            submitList(list) { deferred.complete(list) }
        }

        override fun getItemViewType(position: Int) = getItem(position).type
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
                Manager.createViewHolder(LayoutInflater.from(parent.context), parent, viewType)
        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = getItem(position).bindTo(holder)
    }

    @RequiresApi(29)
    val startRepeater = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
        if (granted) app.startServiceWithLocation<RepeaterService>(requireContext()) else {
            Snackbar.make((activity as MainActivity).binding.fragmentHolder,
                R.string.repeater_missing_location_permissions, Snackbar.LENGTH_LONG).show()
        }
    }
    val startLocalOnlyHotspot = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
        adapter.localOnlyHotspotManager.start(requireContext())
    }
    @RequiresApi(31)
    val requestBluetooth = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
        if (granted) adapter.bluetoothManager!!.ensureInit(requireContext())
    }

    var ifaceLookup: Map<String, NetworkInterface> = emptyMap()
    var enabledTypes = emptySet<TetherType>()
    private lateinit var binding: FragmentTetheringBinding
    var binder: TetheringService.Binder? = null
    private val adapter = ManagerAdapter()
    private val receiver = broadcastReceiver { _, intent ->
        adapter.activeIfaces = intent.tetheredIfaces ?: return@broadcastReceiver
        adapter.localOnlyIfaces = intent.localOnlyTetheredIfaces ?: return@broadcastReceiver
        adapter.erroredIfaces = intent.getStringArrayListExtra(TetheringManager.EXTRA_ERRORED_TETHER)
            ?: return@broadcastReceiver
        adapter.updateEnabledTypes()
        adapter.update()
    }

    private fun updateMonitorList(canMonitor: List<String> = emptyList()) {
        val activity = activity as? MainActivity
        val item = activity?.binding?.toolbar?.menu?.findItem(R.id.monitor) ?: return   // assuming no longer foreground
        item.isNotGone = canMonitor.isNotEmpty()
        item.subMenu!!.apply {
            clear()
            for (iface in canMonitor.sorted()) add(iface).setOnMenuItemClickListener {
                activity.startForegroundService(Intent(activity, TetheringService::class.java)
                        .putExtra(TetheringService.EXTRA_ADD_INTERFACE_MONITOR, iface))
                true
            }
        }
    }

    private var apConfigurationRunning = false
    override fun onMenuItemClick(item: MenuItem?): Boolean {
        return when (item?.itemId) {
            R.id.configuration -> item.subMenu!!.run {
                findItem(R.id.configuration_repeater).isNotGone = Services.p2p != null
                findItem(R.id.configuration_temp_hotspot).isNotGone =
                    adapter.localOnlyHotspotManager.binder?.configuration != null
                true
            }
            R.id.configuration_repeater -> {
                adapter.repeaterManager.configure()
                true
            }
            R.id.configuration_temp_hotspot -> {
                WifiApDialogFragment().apply {
                    arg(WifiApDialogFragment.Arg(adapter.localOnlyHotspotManager.binder?.configuration ?: return false,
                            readOnly = true))
                    // no need for callback
                }.showAllowingStateLoss(parentFragmentManager)
                true
            }
            R.id.configuration_ap -> if (apConfigurationRunning) false else {
                apConfigurationRunning = true
                viewLifecycleOwner.lifecycleScope.launch {
                    val configuration = try {
                        if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") {
                            WifiApManager.configurationLegacy?.toCompat() ?: SoftApConfigurationCompat()
                        } else WifiApManager.configuration.toCompat()
                    } catch (e: InvocationTargetException) {
                        if (e.targetException !is SecurityException) Timber.w(e)
                        try {
                            if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") {
                                RootManager.use { it.execute(WifiApCommands.GetConfigurationLegacy()) }?.toCompat()
                                    ?: SoftApConfigurationCompat()
                            } else RootManager.use { it.execute(WifiApCommands.GetConfiguration()) }.toCompat()
                        } catch (_: CancellationException) {
                            return@launch
                        } catch (eRoot: Exception) {
                            eRoot.addSuppressed(e)
                            if (Build.VERSION.SDK_INT >= 29 || eRoot.getRootCause() !is SecurityException) {
                                Timber.w(eRoot)
                            }
                            SmartSnackbar.make(eRoot).show()
                            return@launch
                        }
                    } catch (e: IllegalArgumentException) {
                        Timber.w(e)
                        SmartSnackbar.make(e).show()
                        return@launch
                    }
                    withStarted {
                        WifiApDialogFragment().apply {
                            arg(WifiApDialogFragment.Arg(configuration))
                            key()
                        }.showAllowingStateLoss(parentFragmentManager)
                    }
                    apConfigurationRunning = false
                }
                true
            }
            else -> false
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        AlertDialogFragment.setResultListener<WifiApDialogFragment, WifiApDialogFragment.Arg>(this) { which, ret ->
            if (which == DialogInterface.BUTTON_POSITIVE) GlobalScope.launch {
                val configuration = ret!!.configuration
                if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") {
                    if (configuration.isAutoShutdownEnabled != TetherTimeoutMonitor.enabled) try {
                        TetherTimeoutMonitor.setEnabled(configuration.isAutoShutdownEnabled)
                    } catch (e: Exception) {
                        Timber.w(e)
                        SmartSnackbar.make(e).show()
                    }
                    val wc = configuration.toWifiConfiguration()
                    try {
                        if (WifiApManager.setConfiguration(wc)) return@launch
                    } catch (e: InvocationTargetException) {
                        try {
                            if (RootManager.use { it.execute(WifiApCommands.SetConfigurationLegacy(wc)) }
                                .value) return@launch
                        } catch (e: CancellationException) {
                            return@launch SmartSnackbar.make(e).show()
                        } catch (eRoot: Exception) {
                            eRoot.addSuppressed(e)
                            Timber.w(eRoot)
                            return@launch SmartSnackbar.make(eRoot).show()
                        }
                    }
                } else {
                    val platform = try {
                        configuration.toPlatform()
                    } catch (e: InvocationTargetException) {
                        Timber.w(e)
                        return@launch SmartSnackbar.make(e).show()
                    }
                    try {
                        if (WifiApManager.setConfiguration(platform)) return@launch
                    } catch (e: InvocationTargetException) {
                        try {
                            if (RootManager.use { it.execute(WifiApCommands.SetConfiguration(platform)) }
                                    .value) return@launch
                        } catch (e: CancellationException) {
                            return@launch SmartSnackbar.make(e).show()
                        } catch (eRoot: Exception) {
                            eRoot.addSuppressed(e)
                            Timber.w(eRoot)
                            return@launch SmartSnackbar.make(eRoot).show()
                        }
                    }
                }
                SmartSnackbar.make(R.string.configuration_rejected).show()
            }
        }
        binding = FragmentTetheringBinding.inflate(inflater, container, false)
        binding.interfaces.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
        binding.interfaces.itemAnimator = DefaultItemAnimator()
        binding.interfaces.adapter = adapter
        adapter.update()
        ServiceForegroundConnector(this, this, TetheringService::class)
        (activity as MainActivity).binding.toolbar.apply {
            inflateMenu(R.menu.toolbar_tethering)
            setOnMenuItemClickListener(this@TetheringFragment)
        }
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        (activity as MainActivity).binding.toolbar.apply {
            menu.clear()
            setOnMenuItemClickListener(null)
        }
    }

    override fun onResume() {
        super.onResume()
        ManageBar.Data.notifyChange()
    }

    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        binder = service as TetheringService.Binder
        service.routingsChanged[this] = {
            lifecycleScope.launch {
                withStarted { adapter.update() }
            }
        }
        requireContext().registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
        if (Build.VERSION.SDK_INT >= 30) {
            TetheringManager.registerTetheringEventCallback(null, adapter)
            TetherType.listener[this] = {
                lifecycleScope.launch { adapter.notifyTetherTypeChanged() }
            }
        }
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        (binder ?: return).routingsChanged -= this
        binder = null
        if (Build.VERSION.SDK_INT >= 30) {
            TetherType.listener -= this
            TetheringManager.unregisterTetheringEventCallback(adapter)
            adapter.lastErrors.clear()
        }
        requireContext().unregisterReceiver(receiver)
    }
}
