//! RFC-0018: Gateway Protocol
//!
//! layer 1: Coordination Layer
//! Handles NAT hole punching, peer discovery, and relay announcements.
//! Gateways do NOT forward data traffic.

const std = @import("std");
const dht = @import("dht");

pub const Gateway = struct {
    allocator: std.mem.Allocator,

    // DHT for peer discovery
    dht_service: *dht.DhtService,

    // In-memory address registry (PeerID -> Public Address)
    // This is a fast lookup for connected peers or those recently announced.
    peer_addresses: std.AutoHashMap(dht.NodeId, std.net.Address),

    pub fn init(allocator: std.mem.Allocator, dht_service: *dht.DhtService) Gateway {
        return Gateway{
            .allocator = allocator,
            .dht_service = dht_service,
            .peer_addresses = std.AutoHashMap(dht.NodeId, std.net.Address).init(allocator),
        };
    }

    pub fn deinit(self: *Gateway) void {
        self.peer_addresses.deinit();
    }

    /// Register a peer's public address
    pub fn registerPeer(self: *Gateway, peer_id: dht.NodeId, addr: std.net.Address) !void {
        // Store in local cache
        try self.peer_addresses.put(peer_id, addr);

        // Announce to DHT (Store operations would go here)
        // For now, we update the local routing table if appropriate,
        // but typically a Gateway *stores* values for others.
        // The current DhtService implementation is basic (RoutingTable only).
        // We'll treat the routing table as the primary storage for "live" nodes.
        const remote = dht.RemoteNode{
            .id = peer_id,
            .address = addr,
            .last_seen = std.time.milliTimestamp(),
        };
        try self.dht_service.routing_table.update(remote);
    }

    /// STUN-like coordination for hole punching
    pub fn coordinateHolePunch(
        self: *Gateway,
        peer_a: dht.NodeId,
        peer_b: dht.NodeId,
    ) !HolePunchCoordination {
        const addr_a = self.peer_addresses.get(peer_a) orelse return error.PeerNotFound;
        const addr_b = self.peer_addresses.get(peer_b) orelse return error.PeerNotFound;

        return HolePunchCoordination{
            .peer_a_addr = addr_a,
            .peer_b_addr = addr_b,
            .timestamp = @intCast(std.time.timestamp()),
        };
    }
};

pub const HolePunchCoordination = struct {
    peer_a_addr: std.net.Address,
    peer_b_addr: std.net.Address,
    timestamp: u64,
};

test "Gateway: register and coordinate" {
    const allocator = std.testing.allocator;

    var self_id = [_]u8{0} ** 32;
    self_id[0] = 1;

    var dht_svc = dht.DhtService.init(allocator, self_id);
    defer dht_svc.deinit();

    var gw = Gateway.init(allocator, &dht_svc);
    defer gw.deinit();

    var peer_a_id = [_]u8{0} ** 32;
    peer_a_id[0] = 0xAA;
    var peer_b_id = [_]u8{0} ** 32;
    peer_b_id[0] = 0xBB;

    const addr_a = try std.net.Address.parseIp("1.2.3.4", 8080);
    const addr_b = try std.net.Address.parseIp("5.6.7.8", 9090);

    try gw.registerPeer(peer_a_id, addr_a);
    try gw.registerPeer(peer_b_id, addr_b);

    const coord = try gw.coordinateHolePunch(peer_a_id, peer_b_id);

    try std.testing.expect(coord.peer_a_addr.eql(addr_a));
    try std.testing.expect(coord.peer_b_addr.eql(addr_b));
}
