package net.hivetechnology.o20010_06.vpn

import android.net.ConnectivityManager
import android.net.LinkAddress
import android.net.VpnService
import net.hivetechnology.o20010_06.model.VpnConfiguration
import java.net.Inet4Address
import java.net.InetAddress

data class PlannedRoute(val address: String, val prefixLength: Int)
data class RoutePlan(val routes: List<PlannedRoute>, val skipped: List<PlannedRoute>)
class InvalidOverlayAddressException : IllegalArgumentException()
class VpnNetworkConflictException(val address: String, val prefixLength: Int) : IllegalArgumentException()
private data class Ipv4Prefix(val address: Inet4Address, val prefixLength: Int, val network: Long)

object RoutePlanner {
    fun apply(builder: VpnService.Builder, configuration: VpnConfiguration, connectivity: ConnectivityManager): RoutePlan {
        val physicalNetworks = connectivity.allNetworks.flatMap { network ->
            val capabilities = connectivity.getNetworkCapabilities(network) ?: return@flatMap emptyList()
            if (capabilities.hasTransport(android.net.NetworkCapabilities.TRANSPORT_VPN)) return@flatMap emptyList()
            val properties = connectivity.getLinkProperties(network) ?: return@flatMap emptyList()
            properties.linkAddresses.mapNotNull(::prefixFromLinkAddress) +
                properties.routes.mapNotNull { route ->
                    val destination = route.destination
                    networkPrefix(destination.address as? Inet4Address, destination.prefixLength)
                        ?.takeIf { it.prefixLength > 0 }
                }
        }

        val overlay = networkPrefix(configuration.overlay.address, configuration.overlay.prefixLength)
            ?: throw InvalidOverlayAddressException()
        if (physicalNetworks.any { overlaps(overlay, it) }) {
            throw VpnNetworkConflictException(overlay.address.hostAddress.orEmpty(), overlay.prefixLength)
        }
        val accepted = linkedSetOf(overlay)
        val skipped = linkedSetOf<Ipv4Prefix>()
        configuration.routes.forEach { route ->
            val prefix = networkPrefix(route.address, maskToPrefix(route.mask)) ?: return@forEach
            if (physicalNetworks.any { overlaps(prefix, it) }) skipped += prefix else accepted += prefix
        }
        accepted.forEach { builder.addRoute(it.address, it.prefixLength) }
        return RoutePlan(
            accepted.map { PlannedRoute(it.address.hostAddress.orEmpty(), it.prefixLength) },
            skipped.map { PlannedRoute(it.address.hostAddress.orEmpty(), it.prefixLength) },
        )
    }

    fun maskToPrefix(mask: String): Int {
        val bytes = parseIpv4(mask)?.address ?: return -1
        var prefix = 0
        var zeroSeen = false
        bytes.forEach { byte ->
            for (bit in 7 downTo 0) {
                val set = (byte.toInt() and (1 shl bit)) != 0
                if (set && zeroSeen) return -1
                if (set) prefix += 1 else zeroSeen = true
            }
        }
        return prefix
    }

    internal fun networksOverlap(leftAddress: String, leftPrefix: Int, rightAddress: String, rightPrefix: Int): Boolean {
        val left = networkPrefix(leftAddress, leftPrefix) ?: return false
        val right = networkPrefix(rightAddress, rightPrefix) ?: return false
        return overlaps(left, right)
    }

    private fun prefixFromLinkAddress(address: LinkAddress): Ipv4Prefix? =
        networkPrefix(address.address as? Inet4Address, address.prefixLength)

    private fun networkPrefix(address: String, prefixLength: Int): Ipv4Prefix? {
        if (prefixLength !in 0..32) return null
        val ipv4 = parseIpv4(address) ?: return null
        return networkPrefix(ipv4, prefixLength)
    }

    private fun parseIpv4(value: String): Inet4Address? {
        val parts = value.split('.')
        if (parts.size != 4) return null
        val bytes = ByteArray(4)
        parts.forEachIndexed { index, part ->
            if (part.isEmpty() || part.any { !it.isDigit() }) return null
            val octet = part.toIntOrNull()?.takeIf { it in 0..255 } ?: return null
            bytes[index] = octet.toByte()
        }
        return InetAddress.getByAddress(bytes) as Inet4Address
    }

    private fun networkPrefix(address: Inet4Address?, prefixLength: Int): Ipv4Prefix? {
        if (address == null || prefixLength !in 0..32) return null
        val mask = prefixMask(prefixLength)
        val network = ipv4Long(address) and mask
        val bytes = byteArrayOf(
            (network shr 24).toByte(),
            (network shr 16).toByte(),
            (network shr 8).toByte(),
            network.toByte(),
        )
        val normalized = InetAddress.getByAddress(bytes) as Inet4Address
        return Ipv4Prefix(normalized, prefixLength, network)
    }

    private fun overlaps(left: Ipv4Prefix, right: Ipv4Prefix): Boolean {
        val mask = prefixMask(minOf(left.prefixLength, right.prefixLength))
        return (left.network and mask) == (right.network and mask)
    }

    private fun prefixMask(prefixLength: Int): Long = when (prefixLength) {
        0 -> 0L
        else -> (0xffff_ffffL shl (32 - prefixLength)) and 0xffff_ffffL
    }

    private fun ipv4Long(address: Inet4Address): Long = address.address.fold(0L) { value, byte ->
        (value shl 8) or (byte.toLong() and 0xffL)
    }
}
