package wang.yaojia.webterm.wire

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

/**
 * The canonical `:wire-protocol` [HostClassifier] (plan §5.4 tiers). This type lives here (shared by
 * :api-client + :client-tls), so its coverage is asserted in the module that OWNS it.
 */
class HostClassifierTest {

    @Test
    fun `loopback hosts classify as LOOPBACK`() {
        assertEquals(HostNetworkTier.LOOPBACK, HostClassifier.classify("localhost"))
        assertEquals(HostNetworkTier.LOOPBACK, HostClassifier.classify("LOCALHOST"))
        assertEquals(HostNetworkTier.LOOPBACK, HostClassifier.classify("::1"))
        assertEquals(HostNetworkTier.LOOPBACK, HostClassifier.classify("[::1]"))
        assertEquals(HostNetworkTier.LOOPBACK, HostClassifier.classify("127.0.0.1"))
        assertEquals(HostNetworkTier.LOOPBACK, HostClassifier.classify("127.5.9.1")) // whole 127/8
    }

    @Test
    fun `RFC1918 + link-local + mDNS classify as PRIVATE_LAN`() {
        assertEquals(HostNetworkTier.PRIVATE_LAN, HostClassifier.classify("10.0.0.5"))
        assertEquals(HostNetworkTier.PRIVATE_LAN, HostClassifier.classify("192.168.1.7"))
        assertEquals(HostNetworkTier.PRIVATE_LAN, HostClassifier.classify("172.16.0.1"))
        assertEquals(HostNetworkTier.PRIVATE_LAN, HostClassifier.classify("172.31.255.254"))
        assertEquals(HostNetworkTier.PRIVATE_LAN, HostClassifier.classify("169.254.10.10"))
        assertEquals(HostNetworkTier.PRIVATE_LAN, HostClassifier.classify("mymac.local"))
        // 172.15 / 172.32 are OUTSIDE 172.16/12 → public (boundary).
        assertEquals(HostNetworkTier.PUBLIC, HostClassifier.classify("172.15.0.1"))
        assertEquals(HostNetworkTier.PUBLIC, HostClassifier.classify("172.32.0.1"))
    }

    @Test
    fun `Tailscale CGNAT + MagicDNS classify as TAILSCALE`() {
        assertEquals(HostNetworkTier.TAILSCALE, HostClassifier.classify("100.64.0.1"))
        assertEquals(HostNetworkTier.TAILSCALE, HostClassifier.classify("100.127.255.254"))
        assertEquals(HostNetworkTier.TAILSCALE, HostClassifier.classify("mybox.tail1234.ts.net"))
        // 100.63 / 100.128 are OUTSIDE 100.64/10 → public.
        assertEquals(HostNetworkTier.PUBLIC, HostClassifier.classify("100.63.0.1"))
        assertEquals(HostNetworkTier.PUBLIC, HostClassifier.classify("100.128.0.1"))
    }

    @Test
    fun `unknown, malformed, out-of-range and IPv6 fall back to PUBLIC (fail-safe)`() {
        assertEquals(HostNetworkTier.PUBLIC, HostClassifier.classify("example.com"))
        assertEquals(HostNetworkTier.PUBLIC, HostClassifier.classify("8.8.8.8"))
        assertEquals(HostNetworkTier.PUBLIC, HostClassifier.classify("999.1.1.1")) // out-of-range octet
        assertEquals(HostNetworkTier.PUBLIC, HostClassifier.classify("10.0.0")) // wrong arity
        assertEquals(HostNetworkTier.PUBLIC, HostClassifier.classify("10.0.0.x")) // non-numeric
        assertEquals(HostNetworkTier.PUBLIC, HostClassifier.classify("fd00::1")) // ULA IPv6 → PUBLIC
        assertEquals(HostNetworkTier.PUBLIC, HostClassifier.classify(""))
    }

    @Test
    fun `ipv4Octets is a strict dotted-quad parser`() {
        assertEquals(listOf(192, 168, 0, 1), HostClassifier.ipv4Octets("192.168.0.1"))
        assertNull(HostClassifier.ipv4Octets("192.168.0")) // too few
        assertNull(HostClassifier.ipv4Octets("192.168.0.1.2")) // too many
        assertNull(HostClassifier.ipv4Octets("192.168.0.256")) // out of range
        assertNull(HostClassifier.ipv4Octets("a.b.c.d")) // non-numeric
    }

    @Test
    fun `classify(endpoint) derives the host, and a hostless baseUrl is PUBLIC (fail-safe)`() {
        val lan = HostEndpoint.fromBaseUrl("http://192.168.1.50:3000")!!
        assertEquals(HostNetworkTier.PRIVATE_LAN, HostClassifier.classify(lan))
        assertEquals("192.168.1.50", HostClassifier.hostOf(lan))

        val loop = HostEndpoint.fromBaseUrl("http://localhost:3000")!!
        assertEquals(HostNetworkTier.LOOPBACK, HostClassifier.classify(loop))
    }

    @Test
    fun `TimelineEvent flags known vs unknown event classes`() {
        val tool = TimelineEvent(at = 1L, eventClass = "tool", toolName = "bash", label = "ran bash")
        assertTrue(tool.hasKnownClass)
        val unknown = TimelineEvent(at = 2L, eventClass = "future-kind", label = "x")
        assertFalse(unknown.hasKnownClass)
        assertTrue("user" in TimelineEvent.KNOWN_CLASSES)
        assertEquals(5, TimelineEvent.KNOWN_CLASSES.size)
    }
}
