//! QVL FFI - C ABI Exports for L2 Integration
//!
//! Provides C-compatible interface for:
//! - Trust scoring
//! - Proof-of-Path verification
//! - Betrayal detection (Bellman-Ford)
//! - Graph mutations
//!
//! Thread Safety: Single-threaded only (initial version)

const std = @import("std");
const qvl = @import("qvl.zig");
const pop_mod = @import("proof_of_path.zig");
const trust_graph = @import("trust_graph");
const time = @import("time");
const slash = @import("slash");

const RiskGraph = qvl.types.RiskGraph;
const RiskEdge = qvl.types.RiskEdge;
const ReputationMap = qvl.pop.ReputationMap;
const ProofOfPath = pop_mod.ProofOfPath;
const PathVerdict = pop_mod.PathVerdict;
const SovereignTimestamp = time.SovereignTimestamp;

// ============================================================================
// OPAQUE CONTEXT
// ============================================================================

/// Opaque handle for QVL context (hides Zig internals)
pub const QvlContext = struct {
    allocator: std.mem.Allocator,
    risk_graph: RiskGraph,
    reputation: ReputationMap,
    trust_graph: trust_graph.CompactTrustGraph,
};

// ============================================================================
// C ABI TYPES
// ============================================================================

pub const PopVerdict = enum(c_int) {
    valid = 0,
    invalid_endpoints = 1,
    broken_link = 2,
    revoked = 3,
    replay = 4,
};

pub const AnomalyReason = enum(u8) {
    none = 0,
    negative_cycle = 1,
    low_coverage = 2,
    bp_divergence = 3,
};

pub const AnomalyScore = extern struct {
    node: u32,
    score: f64, // 0.0-1.0
    reason: u8, // AnomalyReason enum
};

pub const RiskEdgeC = extern struct {
    from: u32,
    to: u32,
    risk: f64,
    timestamp_ns: u64,
    nonce: u64,
    level: u8,
    expires_at_ns: u64,
};

// ============================================================================
// CONTEXT MANAGEMENT
// ============================================================================

/// Initialize QVL context
/// Returns NULL on allocation failure
export fn qvl_init() callconv(.c) ?*QvlContext {
    // Use C allocator for FFI (heap allocations)
    const allocator = std.heap.c_allocator;

    const ctx = allocator.create(QvlContext) catch return null;
    const default_root: [32]u8 = [_]u8{0} ** 32;
    ctx.* = .{
        .allocator = allocator,
        .risk_graph = RiskGraph.init(allocator),
        .reputation = ReputationMap.init(allocator),
        .trust_graph = trust_graph.CompactTrustGraph.init(allocator, default_root, .{}) catch {
            allocator.destroy(ctx);
            return null;
        },
    };

    return ctx;
}

/// Cleanup and free QVL context
export fn qvl_deinit(ctx: ?*QvlContext) callconv(.c) void {
    const context = ctx orelse return;
    context.risk_graph.deinit();
    context.reputation.deinit();
    context.trust_graph.deinit();
    context.allocator.destroy(context);
}

// ============================================================================
// TRUST SCORING
// ============================================================================

/// Get trust score for a DID
/// Returns -1.0 on error (invalid DID, not found, etc.)
export fn qvl_get_trust_score(
    ctx: ?*QvlContext,
    did: [*c]const u8,
    did_len: usize,
) callconv(.c) f64 {
    const context = ctx orelse return -1.0;
    if (did_len != 32) return -1.0; // DID must be 32 bytes

    const did_bytes = did[0..did_len];
    var did_array: [32]u8 = undefined;
    @memcpy(&did_array, did_bytes);

    // Hash DID to NodeId (simplified; real impl would use node_map)
    var hasher = std.hash.Wyhash.init(0);
    hasher.update(&did_array);
    const node_id: u32 = @truncate(hasher.final());

    return context.reputation.get(node_id);
}

/// Get reputation score for a NodeId
/// Returns -1.0 on error
export fn qvl_get_reputation(ctx: ?*QvlContext, node_id: u32) callconv(.c) f64 {
    const context = ctx orelse return -1.0;
    return context.reputation.get(node_id);
}

// ============================================================================
// PROOF-OF-PATH
// ============================================================================

/// Verify a serialized PoP proof
export fn qvl_verify_pop(
    ctx: ?*QvlContext,
    proof_bytes: [*c]const u8,
    proof_len: usize,
    sender_did: [*c]const u8,
    receiver_did: [*c]const u8,
) callconv(.c) PopVerdict {
    const context = ctx orelse return .invalid_endpoints;

    // Deserialize proof
    const proof_slice = proof_bytes[0..proof_len];
    var proof = ProofOfPath.deserialize(context.allocator, proof_slice) catch {
        return .invalid_endpoints;
    };
    defer proof.deinit();

    // Copy DIDs
    var sender: [32]u8 = undefined;
    var receiver: [32]u8 = undefined;
    @memcpy(&sender, sender_did[0..32]);
    @memcpy(&receiver, receiver_did[0..32]);

    // Verify
    const verdict = proof.verify(receiver, sender, &context.trust_graph);

    // Convert to C enum
    return switch (verdict) {
        .valid => .valid,
        .invalid_endpoints => .invalid_endpoints,
        .broken_link => .broken_link,
        .revoked => .revoked,
        .replay => .replay,
        else => .invalid_endpoints, // Catch-all for future enum additions
    };
}

// ============================================================================
// BETRAYAL DETECTION
// ============================================================================

/// Run Bellman-Ford betrayal detection from source node
/// Returns anomaly score (0.0 = clean, 0.9+ = critical)
export fn qvl_detect_betrayal(
    ctx: ?*QvlContext,
    source_node: u32,
) callconv(.c) AnomalyScore {
    const context = ctx orelse return .{ .node = 0, .score = 0.0, .reason = @intFromEnum(AnomalyReason.none) };

    var result = qvl.betrayal.detectBetrayal(
        &context.risk_graph,
        source_node,
        context.allocator,
    ) catch {
        return .{ .node = 0, .score = 0.0, .reason = @intFromEnum(AnomalyReason.none) };
    };
    defer result.deinit();

    if (result.betrayal_cycles.items.len > 0) {
        // Betrayal detected - compute anomaly score
        const score = result.computeAnomalyScore();
        return .{
            .node = source_node,
            .score = score,
            .reason = @intFromEnum(AnomalyReason.negative_cycle),
        };
    }

    return .{ .node = source_node, .score = 0.0, .reason = @intFromEnum(AnomalyReason.none) };
}

// ============================================================================
// GRAPH MUTATIONS
// ============================================================================

/// Add trust edge to risk graph
/// Returns 0 on success, non-zero on error
export fn qvl_add_trust_edge(
    ctx: ?*QvlContext,
    edge_c: [*c]const RiskEdgeC,
) callconv(.c) c_int {
    const context = ctx orelse return -1;
    const edge_ptr = edge_c orelse return -1;
    const edge_val = edge_ptr.*;

    const edge = RiskEdge{
        .from = edge_val.from,
        .to = edge_val.to,
        .risk = edge_val.risk,
        .timestamp = SovereignTimestamp.fromNanoseconds(edge_val.timestamp_ns, .unix_1970),
        .nonce = edge_val.nonce,
        .level = edge_val.level,
        .expires_at = SovereignTimestamp.fromNanoseconds(edge_val.expires_at_ns, .unix_1970),
    };

    context.risk_graph.addEdge(edge) catch return -2;
    return 0;
}

/// Revoke trust edge
/// Returns 0 on success, non-zero on error (not found, etc.)
export fn qvl_revoke_trust_edge(
    ctx: ?*QvlContext,
    from: u32,
    to: u32,
) callconv(.c) c_int {
    const context = ctx orelse return -1;

    // Find and remove edge
    var i: usize = 0;
    while (i < context.risk_graph.edges.items.len) : (i += 1) {
        const edge = &context.risk_graph.edges.items[i];
        if (edge.from == from and edge.to == to) {
            _ = context.risk_graph.edges.swapRemove(i);
            return 0;
        }
    }

    return -2; // Not found
}

/// Get DID for a given node ID
/// writes 32 bytes to out_did
/// returns true on success
export fn qvl_get_did(
    ctx: ?*QvlContext,
    node_id: u32,
    out_did: [*c]u8,
) callconv(.c) bool {
    const context = ctx orelse return false;
    if (out_did == null) return false;

    if (context.trust_graph.getDid(node_id)) |did| {
        @memcpy(out_did[0..32], &did);
        return true;
    }
    return false;
}

/// Register a DID and get its node ID
/// returns true on success, ID in out_id
export fn qvl_register_node(
    ctx: ?*QvlContext,
    did_ptr: [*c]const u8,
    out_id: [*c]u32,
) callconv(.c) bool {
    const context = ctx orelse return false;
    if (did_ptr == null or out_id == null) return false;

    var did: [32]u8 = undefined;
    @memcpy(&did, did_ptr[0..32]);

    if (context.trust_graph.getOrInsertNode(did)) |id| {
        out_id.* = id;
        // Ensure node exists in risk graph (idempotent check needed? RiskGraph is simple list?)
        // RiskGraph.addNode appends. We don't want duplicates.
        // But RiskGraph doesn't have hasNode(id).
        // For Phase 8/9 simulation, we assume register is called once per node.
        // Or we check adjacency?
        // Let's just append for now. Duplicate iter in BellmanFord increases N but harmless?
        // BellmanFord iterates nodes to init dist. If duplicates, it inits twice. Harmless.
        context.risk_graph.addNode(id) catch {};
        return true;
    } else |_| {
        return false;
    }
}

/// Get serialization of betrayal evidence (for hashing/storage)
/// out_buf must be large enough. returns actual len written.
/// if out_buf is null, returns required len.
export fn qvl_get_betrayal_evidence(
    ctx: ?*QvlContext,
    node_id: u32,
    out_buf: [*c]u8,
    buf_len: u32,
) callconv(.c) u32 {
    const context = ctx orelse return 0;

    var result = qvl.betrayal.detectBetrayal(
        &context.risk_graph,
        node_id,
        context.allocator,
    ) catch return 0;
    defer result.deinit();

    if (result.betrayal_cycles.items.len == 0) return 0;

    const evidence = result.generateEvidence(&context.risk_graph, context.allocator) catch return 0;
    defer context.allocator.free(evidence);

    if (out_buf == null) return @intCast(evidence.len);
    if (buf_len < evidence.len) return 0; // Buffer too small

    @memcpy(out_buf[0..evidence.len], evidence);
    return @intCast(evidence.len);
}

/// Issue a SlashSignal for a detected betrayal
/// Returns 0 on success, < 0 on error
/// If 'out_signal' is non-null, writes serialized signal (82 bytes)
export fn qvl_issue_slash_signal(
    ctx: ?*QvlContext,
    target_did: [*c]const u8,
    reason: u8,
    evidence_hash: [*c]const u8,
    out_signal: [*c]u8,
) callconv(.c) c_int {
    _ = ctx;
    if (target_did == null) return -2;

    var did: [32]u8 = undefined;
    @memcpy(&did, target_did[0..32]);

    var hash: [32]u8 = [_]u8{0} ** 32;
    if (evidence_hash != null) {
        @memcpy(&hash, evidence_hash[0..32]);
    }

    const signal = slash.SlashSignal{
        .target_did = did,
        .reason = @enumFromInt(reason),
        .severity = .Quarantine,
        .evidence_hash = hash,
        .timestamp = @intCast(std.time.timestamp()),
        .duration_seconds = 86400,
        .entropy_stamp = 0,
    };

    if (out_signal != null) {
        const bytes = signal.serializeForSigning();
        @memcpy(out_signal[0..82], &bytes);
    }

    return 0;
}

// ============================================================================
// TESTS (C ABI validation)
// ============================================================================

test "FFI: context lifecycle" {
    const ctx = qvl_init();
    try std.testing.expect(ctx != null);
    qvl_deinit(ctx);
}

test "FFI: trust scoring" {
    const ctx = qvl_init() orelse return error.InitFailed;
    defer qvl_deinit(ctx);

    const score = qvl_get_reputation(ctx, 42);
    try std.testing.expectEqual(score, 0.5); // Default neutral
}

test "FFI: add edge" {
    const ctx = qvl_init() orelse return error.InitFailed;
    defer qvl_deinit(ctx);

    const edge = RiskEdgeC{
        .from = 0,
        .to = 1,
        .risk = 0.5,
        .timestamp_ns = 1000,
        .nonce = 0,
        .level = 3,
        .expires_at_ns = 2000,
    };

    const result = qvl_add_trust_edge(ctx, &edge);
    try std.testing.expectEqual(result, 0);
}
