import Foundation
import WireProtocol

/// The four network tiers of plan §5.4's pairing/warning table — PUBLIC so UI
/// layers (PairingViewModel's scheme+address 分层提示, T-iOS-12) consume ONE
/// classification instead of re-deriving it (the W3 dedup this type exists for).
///
/// | tier | 匹配 | §5.4 提示 |
/// |---|---|---|
/// | `loopback` | localhost · 127.0.0.0/8 · ::1 | 无警告 |
/// | `privateLAN` | RFC1918 (10/8, 172.16/12, 192.168/16) · link-local 169.254/16 · mDNS `.local` | ws:// 非阻断明文提示 |
/// | `tailscale` | CGNAT 100.64.0.0/10 · MagicDNS `*.ts.net` | 无明文警告（WireGuard 已加密） |
/// | `public` | 其余一切（含越界/畸形地址——fail-safe：分不清就当公网，宁可多警告） | 最强阻断式警告 |
public enum HostNetworkTier: Sendable, Equatable, CaseIterable {
    case loopback
    case privateLAN
    case tailscale
    case `public`
}

/// Pure, stateless classifier over a URL host string (plan §5.4 tiers).
/// IPv6 note: only loopback `::1` is tiered; other IPv6 literals (incl. ULA
/// fd00::/8) fall to `.public` — fail-safe over-warning, matching v1 scope
/// (the app dials IPv4 LAN / CGNAT / MagicDNS targets).
public enum HostClassifier {
    private static let magicDNSSuffix = ".ts.net"
    private static let mdnsSuffix = ".local"
    private static let loopbackNames: Set<String> = ["localhost", "::1", "[::1]"]

    /// Classify a bare host string (as returned by `URL.host` — no scheme,
    /// no port). Unknown/malformed input is `.public` by design (fail-safe).
    public static func classify(host: String) -> HostNetworkTier {
        let lowered = host.lowercased()
        if loopbackNames.contains(lowered) {
            return .loopback
        }
        if lowered.hasSuffix(magicDNSSuffix) {
            return .tailscale
        }
        if lowered.hasSuffix(mdnsSuffix) {
            return .privateLAN
        }
        guard let octets = ipv4Octets(lowered) else {
            return .public
        }
        return classifyIPv4(octets)
    }

    /// Convenience for callers holding a `HostEndpoint` (the VM's shape).
    public static func classify(endpoint: HostEndpoint) -> HostNetworkTier {
        classify(host: endpoint.baseURL.host ?? "")
    }

    // MARK: - IPv4 (shared with PairingError's Local-Network diagnosis)

    private static func classifyIPv4(_ octets: [Int]) -> HostNetworkTier {
        switch (octets[0], octets[1]) {
        case (127, _):                 // loopback 127/8
            return .loopback
        case (10, _), (192, 168):      // RFC1918 10/8 · 192.168/16
            return .privateLAN
        case (172, 16...31):           // RFC1918 172.16/12
            return .privateLAN
        case (169, 254):               // link-local
            return .privateLAN
        case (100, 64...127):          // CGNAT 100.64/10 (Tailscale)
            return .tailscale
        default:
            return .public
        }
    }

    private static let ipv4OctetCount = 4
    private static let ipv4OctetRange = 0...255

    /// Strict dotted-quad parse; anything else (hostnames, IPv6, out-of-range
    /// octets, wrong arity) → nil.
    static func ipv4Octets(_ host: String) -> [Int]? {
        let parts = host.split(separator: ".", omittingEmptySubsequences: false)
        guard parts.count == ipv4OctetCount else { return nil }
        let octets = parts.compactMap { Int($0) }
        guard octets.count == ipv4OctetCount,
              octets.allSatisfy({ ipv4OctetRange.contains($0) })
        else { return nil }
        return octets
    }
}
