// SPDX-License-Identifier: LCL-1.0
// Copyright (c) 2026 Markus Maiwald
// Stewardship: Self Sovereign Society Foundation
//
// This file is part of the Nexus Commonwealth.
// See legal/LICENSE_COMMONWEALTH.md for license terms.

//! Project LibWeb: LWF Adapter for Rumpk Kernel
//!
//! Freestanding LWF header parser for kernel-side routing decisions.
//! Zero-copy — operates directly on ION slab buffers.
//! Does NOT use std.mem.Allocator — all parsing is in-place.
//!
//! The full LWF codec (with allocation, encode, checksum) runs in
//! the Membrane (userland) where std is available. The kernel only
//! needs to parse the header to decide routing.
//!
//! Wire Format (after Ethernet header):
//!   [Eth 14B][LWF Header 88B][Payload ...][LWF Trailer 36B]
//!
//! Integration Points:
//!   - NetSwitch: EtherType 0x4C57 ("LW") → route to chan_lwf_rx
//!   - Membrane:  Full LWF codec via upstream lwf.zig (uses std)

// =========================================================
// LWF Constants (RFC-0000 v0.3.1)
// =========================================================

pub const ETHERTYPE_LWF: u16 = 0x4C57; // "LW" in ASCII — Sovereign EtherType
pub const LWF_MAGIC = [4]u8{ 'L', 'W', 'F', 0 };
pub const LWF_VERSION: u8 = 0x02;
pub const HEADER_SIZE: usize = 88;
pub const TRAILER_SIZE: usize = 36;
pub const MIN_FRAME_SIZE: usize = HEADER_SIZE + TRAILER_SIZE; // 124 bytes

// =========================================================
// Frame Classes (RFC-0000 Section 4.2)
// =========================================================

pub const FrameClass = enum(u8) {
    micro = 0x00, // 128 bytes total
    mini = 0x01, // 512 bytes total
    standard = 0x02, // 1350 bytes total
    big = 0x03, // 4096 bytes total (exceeds ION slab!)
    jumbo = 0x04, // 9000 bytes total (exceeds ION slab!)
    _,

    pub fn maxTotal(self: FrameClass) u16 {
        return switch (self) {
            .micro => 128,
            .mini => 512,
            .standard => 1350,
            .big => 4096,
            .jumbo => 9000,
            _ => 0,
        };
    }

    /// Check if frame class fits in an ION slab (2048 bytes)
    pub fn fitsInSlab(self: FrameClass) bool {
        return switch (self) {
            .micro, .mini, .standard => true,
            .big, .jumbo => false,
            _ => false,
        };
    }
};

// =========================================================
// Service Types (RFC-0121)
// =========================================================

pub const ServiceType = struct {
    pub const DATA_TRANSPORT: u16 = 0x0001;
    pub const SLASH_PROTOCOL: u16 = 0x0002;
    pub const IDENTITY_SIGNAL: u16 = 0x0003;
    pub const ECONOMIC_SETTLEMENT: u16 = 0x0004;
    pub const RELAY_FORWARD: u16 = 0x0005;
    pub const STREAM_AUDIO: u16 = 0x0800;
    pub const STREAM_VIDEO: u16 = 0x0801;
    pub const STREAM_DATA: u16 = 0x0802;
    pub const SWARM_MANIFEST: u16 = 0x0B00;
    pub const SWARM_HAVE: u16 = 0x0B01;
    pub const SWARM_REQUEST: u16 = 0x0B02;
    pub const SWARM_BLOCK: u16 = 0x0B03;
};

// =========================================================
// LWF Flags (RFC-0000 Section 4.3)
// =========================================================

pub const Flags = struct {
    pub const ENCRYPTED: u8 = 0x01;
    pub const SIGNED: u8 = 0x02;
    pub const RELAYABLE: u8 = 0x04;
    pub const HAS_ENTROPY: u8 = 0x08;
    pub const FRAGMENTED: u8 = 0x10;
    pub const PRIORITY: u8 = 0x20;
};

// =========================================================
// LWF Header View (Zero-Copy over ION slab)
// =========================================================

/// Parsed header fields from a raw buffer. No allocation.
/// All multi-byte integers are stored big-endian on the wire.
pub const HeaderView = struct {
    /// Pointer to the start of the LWF header in the ION slab
    raw: [*]const u8,

    // Pre-parsed routing fields (hot path)
    service_type: u16,
    payload_len: u16,
    frame_class: FrameClass,
    version: u8,
    flags: u8,
    sequence: u32,
    timestamp: u64,

    /// Parse header from raw bytes. Returns null if invalid.
    /// Does NOT copy — references the original buffer.
    pub fn parse(data: [*]const u8, data_len: u16) ?HeaderView {
        if (data_len < HEADER_SIZE) return null;

        // Fast reject: Magic check (4 bytes at offset 0)
        if (data[0] != 'L' or data[1] != 'W' or data[2] != 'F' or data[3] != 0)
            return null;

        // Version check (offset 77)
        const ver = data[77];
        if (ver != LWF_VERSION) return null;

        // Parse routing-critical fields
        const service = readU16Big(data[72..74]);
        const plen = readU16Big(data[74..76]);
        const fclass = data[76];
        const flg = data[78];
        const seq = readU32Big(data[68..72]);
        const ts = readU64Big(data[80..88]);

        // Sanity: payload_len must fit in remaining buffer
        const total_needed = HEADER_SIZE + @as(usize, plen) + TRAILER_SIZE;
        if (total_needed > data_len) return null;

        return HeaderView{
            .raw = data,
            .service_type = service,
            .payload_len = plen,
            .frame_class = @enumFromInt(fclass),
            .version = ver,
            .flags = flg,
            .sequence = seq,
            .timestamp = ts,
        };
    }

    /// Get destination hint (24 bytes at offset 4)
    pub fn destHint(self: *const HeaderView) *const [24]u8 {
        return @ptrCast(self.raw[4..28]);
    }

    /// Get source hint (24 bytes at offset 28)
    pub fn sourceHint(self: *const HeaderView) *const [24]u8 {
        return @ptrCast(self.raw[28..52]);
    }

    /// Get session ID (16 bytes at offset 52)
    pub fn sessionId(self: *const HeaderView) *const [16]u8 {
        return @ptrCast(self.raw[52..68]);
    }

    /// Get pointer to payload data (starts at offset 88)
    pub fn payloadPtr(self: *const HeaderView) [*]const u8 {
        return self.raw + HEADER_SIZE;
    }

    /// Check if frame has PRIORITY flag
    pub fn isPriority(self: *const HeaderView) bool {
        return (self.flags & Flags.PRIORITY) != 0;
    }

    /// Check if frame is encrypted
    pub fn isEncrypted(self: *const HeaderView) bool {
        return (self.flags & Flags.ENCRYPTED) != 0;
    }

    /// Total frame size (header + payload + trailer)
    pub fn totalSize(self: *const HeaderView) usize {
        return HEADER_SIZE + @as(usize, self.payload_len) + TRAILER_SIZE;
    }
};

// =========================================================
// Fast Path: Validation for NetSwitch
// =========================================================

/// Quick magic-byte check. Use before full parse for early rejection.
/// Expects data to point past the Ethernet header (14 bytes).
pub fn isLwfMagic(data: [*]const u8, len: u16) bool {
    if (len < 4) return false;
    return data[0] == 'L' and data[1] == 'W' and data[2] == 'F' and data[3] == 0;
}

/// Validate and parse an LWF frame from an ION slab.
/// Returns the parsed header view, or null if invalid.
/// The ION slab data should start at the LWF header (after Ethernet strip).
pub fn validateFrame(data: [*]const u8, len: u16) ?HeaderView {
    return HeaderView.parse(data, len);
}

// =========================================================
// C ABI Exports (for Nim FFI)
// =========================================================

/// Check if a raw buffer contains a valid LWF frame.
/// Called from netswitch.nim to decide routing.
/// Returns 1 if valid LWF, 0 otherwise.
export fn lwf_validate(data: [*]const u8, len: u16) u8 {
    if (HeaderView.parse(data, len)) |_| {
        return 1;
    }
    return 0;
}

/// Get the service type from a validated LWF frame.
/// Returns 0 on invalid input.
export fn lwf_get_service_type(data: [*]const u8, len: u16) u16 {
    if (HeaderView.parse(data, len)) |hdr| {
        return hdr.service_type;
    }
    return 0;
}

/// Get the payload length from a validated LWF frame.
export fn lwf_get_payload_len(data: [*]const u8, len: u16) u16 {
    if (HeaderView.parse(data, len)) |hdr| {
        return hdr.payload_len;
    }
    return 0;
}

/// Check if frame has PRIORITY flag set.
export fn lwf_is_priority(data: [*]const u8, len: u16) u8 {
    if (HeaderView.parse(data, len)) |hdr| {
        return if (hdr.isPriority()) 1 else 0;
    }
    return 0;
}

// =========================================================
// Freestanding Integer Helpers (no std)
// =========================================================

inline fn readU16Big(bytes: *const [2]u8) u16 {
    return (@as(u16, bytes[0]) << 8) | @as(u16, bytes[1]);
}

inline fn readU32Big(bytes: *const [4]u8) u32 {
    return (@as(u32, bytes[0]) << 24) |
        (@as(u32, bytes[1]) << 16) |
        (@as(u32, bytes[2]) << 8) |
        @as(u32, bytes[3]);
}

inline fn readU64Big(bytes: *const [8]u8) u64 {
    return (@as(u64, bytes[0]) << 56) |
        (@as(u64, bytes[1]) << 48) |
        (@as(u64, bytes[2]) << 40) |
        (@as(u64, bytes[3]) << 32) |
        (@as(u64, bytes[4]) << 24) |
        (@as(u64, bytes[5]) << 16) |
        (@as(u64, bytes[6]) << 8) |
        @as(u64, bytes[7]);
}

inline fn writeU16Big(buf: *[2]u8, val: u16) void {
    buf[0] = @truncate(val >> 8);
    buf[1] = @truncate(val);
}

inline fn writeU32Big(buf: *[4]u8, val: u32) void {
    buf[0] = @truncate(val >> 24);
    buf[1] = @truncate(val >> 16);
    buf[2] = @truncate(val >> 8);
    buf[3] = @truncate(val);
}

inline fn writeU64Big(buf: *[8]u8, val: u64) void {
    buf[0] = @truncate(val >> 56);
    buf[1] = @truncate(val >> 48);
    buf[2] = @truncate(val >> 40);
    buf[3] = @truncate(val >> 32);
    buf[4] = @truncate(val >> 24);
    buf[5] = @truncate(val >> 16);
    buf[6] = @truncate(val >> 8);
    buf[7] = @truncate(val);
}

// =========================================================
// Test Frame Builder (for unit tests only)
// =========================================================

/// Build a minimal valid LWF frame in a buffer for testing.
/// Returns the total frame size written.
fn buildTestFrame(buf: []u8, payload: []const u8, service: u16, flags: u8) usize {
    const total = HEADER_SIZE + payload.len + TRAILER_SIZE;
    if (buf.len < total) return 0;

    // Zero the buffer
    for (buf[0..total]) |*b| b.* = 0;

    // Magic
    buf[0] = 'L';
    buf[1] = 'W';
    buf[2] = 'F';
    buf[3] = 0;

    // dest_hint (4..28) — leave zeros
    // source_hint (28..52) — leave zeros
    // session_id (52..68) — leave zeros

    // Sequence (68..72)
    writeU32Big(buf[68..72], 1);

    // Service type (72..74)
    writeU16Big(buf[72..74], service);

    // Payload len (74..76)
    writeU16Big(buf[74..76], @truncate(payload.len));

    // Frame class (76)
    buf[76] = @intFromEnum(FrameClass.standard);

    // Version (77)
    buf[77] = LWF_VERSION;

    // Flags (78)
    buf[78] = flags;

    // entropy_difficulty (79) — 0

    // Timestamp (80..88)
    writeU64Big(buf[80..88], 0xDEADBEEF);

    // Payload
    for (payload, 0..) |byte, i| {
        buf[HEADER_SIZE + i] = byte;
    }

    // Trailer (zeros = no signature, no checksum)

    return total;
}

// =========================================================
// Tests
// =========================================================

const testing = @import("std").testing;

test "valid LWF frame parses correctly" {
    var buf: [512]u8 = undefined;
    const payload = "Hello LWF";
    const sz = buildTestFrame(&buf, payload, ServiceType.DATA_TRANSPORT, 0);
    try testing.expect(sz > 0);

    const hdr = HeaderView.parse(&buf, @truncate(sz));
    try testing.expect(hdr != null);

    const h = hdr.?;
    try testing.expectEqual(ServiceType.DATA_TRANSPORT, h.service_type);
    try testing.expectEqual(@as(u16, 9), h.payload_len);
    try testing.expectEqual(LWF_VERSION, h.version);
    try testing.expectEqual(@as(u8, 0), h.flags);
    try testing.expectEqual(@as(u32, 1), h.sequence);
    try testing.expectEqual(@as(u64, 0xDEADBEEF), h.timestamp);
    try testing.expectEqual(FrameClass.standard, h.frame_class);
}

test "invalid magic rejected" {
    var buf: [512]u8 = undefined;
    _ = buildTestFrame(&buf, "test", ServiceType.DATA_TRANSPORT, 0);

    // Corrupt magic
    buf[0] = 'X';
    const hdr = HeaderView.parse(&buf, 160);
    try testing.expect(hdr == null);
}

test "wrong version rejected" {
    var buf: [512]u8 = undefined;
    _ = buildTestFrame(&buf, "test", ServiceType.DATA_TRANSPORT, 0);

    // Set wrong version
    buf[77] = 0x01;
    const hdr = HeaderView.parse(&buf, 160);
    try testing.expect(hdr == null);
}

test "buffer too small rejected" {
    var buf: [512]u8 = undefined;
    _ = buildTestFrame(&buf, "test", ServiceType.DATA_TRANSPORT, 0);

    // Pass length smaller than header
    const hdr = HeaderView.parse(&buf, 80);
    try testing.expect(hdr == null);
}

test "payload overflow rejected" {
    var buf: [512]u8 = undefined;
    _ = buildTestFrame(&buf, "test", ServiceType.DATA_TRANSPORT, 0);

    // Claim huge payload that doesn't fit
    writeU16Big(buf[74..76], 5000);
    const hdr = HeaderView.parse(&buf, 160);
    try testing.expect(hdr == null);
}

test "priority flag detection" {
    var buf: [512]u8 = undefined;
    const sz = buildTestFrame(&buf, "urgent", ServiceType.SLASH_PROTOCOL, Flags.PRIORITY);

    const hdr = HeaderView.parse(&buf, @truncate(sz)).?;
    try testing.expect(hdr.isPriority());
    try testing.expect(!hdr.isEncrypted());
}

test "encrypted flag detection" {
    var buf: [512]u8 = undefined;
    const sz = buildTestFrame(&buf, "secret", ServiceType.IDENTITY_SIGNAL, Flags.ENCRYPTED | Flags.SIGNED);

    const hdr = HeaderView.parse(&buf, @truncate(sz)).?;
    try testing.expect(hdr.isEncrypted());
    try testing.expect(!hdr.isPriority());
}

test "isLwfMagic fast path" {
    var buf: [8]u8 = .{ 'L', 'W', 'F', 0, 0, 0, 0, 0 };
    try testing.expect(isLwfMagic(&buf, 8));

    buf[2] = 'X';
    try testing.expect(!isLwfMagic(&buf, 8));

    // Too short
    try testing.expect(!isLwfMagic(&buf, 3));
}

test "C ABI lwf_validate matches HeaderView.parse" {
    var buf: [512]u8 = undefined;
    const sz = buildTestFrame(&buf, "abi_test", ServiceType.DATA_TRANSPORT, 0);

    try testing.expectEqual(@as(u8, 1), lwf_validate(&buf, @truncate(sz)));

    // Corrupt magic
    buf[0] = 0;
    try testing.expectEqual(@as(u8, 0), lwf_validate(&buf, @truncate(sz)));
}

test "C ABI lwf_get_service_type" {
    var buf: [512]u8 = undefined;
    const sz = buildTestFrame(&buf, "svc", ServiceType.ECONOMIC_SETTLEMENT, 0);

    try testing.expectEqual(ServiceType.ECONOMIC_SETTLEMENT, lwf_get_service_type(&buf, @truncate(sz)));
}

test "frame class slab fit check" {
    try testing.expect(FrameClass.micro.fitsInSlab());
    try testing.expect(FrameClass.mini.fitsInSlab());
    try testing.expect(FrameClass.standard.fitsInSlab());
    try testing.expect(!FrameClass.big.fitsInSlab());
    try testing.expect(!FrameClass.jumbo.fitsInSlab());
}

test "totalSize calculation" {
    var buf: [512]u8 = undefined;
    const payload = "12345";
    const sz = buildTestFrame(&buf, payload, ServiceType.DATA_TRANSPORT, 0);

    const hdr = HeaderView.parse(&buf, @truncate(sz)).?;
    try testing.expectEqual(@as(usize, 88 + 5 + 36), hdr.totalSize());
}

test "dest and source hint accessors" {
    var buf: [512]u8 = undefined;
    _ = buildTestFrame(&buf, "hint", ServiceType.DATA_TRANSPORT, 0);

    // Write a known dest hint at offset 4
    buf[4] = 0xAA;
    buf[27] = 0xBB;
    // Write a known source hint at offset 28
    buf[28] = 0xCC;
    buf[51] = 0xDD;

    const hdr = HeaderView.parse(&buf, 160).?;
    try testing.expectEqual(@as(u8, 0xAA), hdr.destHint()[0]);
    try testing.expectEqual(@as(u8, 0xBB), hdr.destHint()[23]);
    try testing.expectEqual(@as(u8, 0xCC), hdr.sourceHint()[0]);
    try testing.expectEqual(@as(u8, 0xDD), hdr.sourceHint()[23]);
}

test "session ID accessor" {
    var buf: [512]u8 = undefined;
    _ = buildTestFrame(&buf, "sess", ServiceType.DATA_TRANSPORT, 0);

    // Write session ID at offset 52
    buf[52] = 0x42;
    buf[67] = 0x99;

    const hdr = HeaderView.parse(&buf, 160).?;
    try testing.expectEqual(@as(u8, 0x42), hdr.sessionId()[0]);
    try testing.expectEqual(@as(u8, 0x99), hdr.sessionId()[15]);
}
