//! RFC-0120 Extension: Bellman-Ford Betrayal Detection
//!
//! Detects negative cycles in the trust graph, which indicate:
//! - Collusion rings (Sybil attacks)
//! - Decade-level betrayals (cascading trust decay)
//! - Cartel behavior (coordinated false vouches)
//!
//! Complexity: O(|V| × |E|) with early exit optimization.

const std = @import("std");
const time = @import("time");
const types = @import("types.zig");

const NodeId = types.NodeId;
const RiskGraph = types.RiskGraph;
const RiskEdge = types.RiskEdge;
const AnomalyScore = types.AnomalyScore;

/// Result of Bellman-Ford betrayal detection.
pub const BellmanFordResult = struct {
    allocator: std.mem.Allocator,
    /// Shortest distances from source (accounting for negative edges)
    distances: std.AutoHashMapUnmanaged(NodeId, f64),
    /// Predecessor map for path reconstruction
    predecessors: std.AutoHashMapUnmanaged(NodeId, ?NodeId),
    /// Detected betrayal cycles (negative cycles)
    betrayal_cycles: std.ArrayListUnmanaged([]NodeId),

    pub fn deinit(self: *BellmanFordResult) void {
        self.distances.deinit(self.allocator);
        self.predecessors.deinit(self.allocator);
        for (self.betrayal_cycles.items) |cycle| {
            self.allocator.free(cycle);
        }
        self.betrayal_cycles.deinit(self.allocator);
    }

    /// Compute anomaly score based on detected cycles.
    /// Score is normalized to [0, 1].
    pub fn computeAnomalyScore(self: *const BellmanFordResult) f64 {
        if (self.betrayal_cycles.items.len > 0) return 1.0; // Any negative cycle is critical
        return 0.0;
    }

    /// Get nodes involved in any betrayal cycle.
    pub fn getCompromisedNodes(self: *const BellmanFordResult, allocator: std.mem.Allocator) ![]NodeId {
        var seen = std.AutoHashMapUnmanaged(NodeId, void){};
        defer seen.deinit(allocator);

        for (self.betrayal_cycles.items) |cycle| {
            for (cycle) |node| {
                try seen.put(allocator, node, {});
            }
        }

        var result = try allocator.alloc(NodeId, seen.count());
        var i: usize = 0;
        var it = seen.keyIterator();
        while (it.next()) |key| {
            result[i] = key.*;
            i += 1;
        }
        return result;
    }

    /// Generate cryptographic evidence of betrayal (serialized cycle with weights)
    /// Format: version(1) + cycle_len(4) + [NodeID(4) + Risk(8)]...
    pub fn generateEvidence(
        self: *const BellmanFordResult,
        graph: *const RiskGraph,
        allocator: std.mem.Allocator,
    ) ![]u8 {
        if (self.betrayal_cycles.items.len == 0) return error.NoEvidence;

        const cycle = self.betrayal_cycles.items[0];
        var evidence = std.ArrayListUnmanaged(u8){};
        errdefer evidence.deinit(allocator);

        try evidence.writer(allocator).writeByte(0x01); // Version
        try evidence.writer(allocator).writeInt(u32, @intCast(cycle.len), .little);

        for (cycle, 0..) |node, i| {
            try evidence.writer(allocator).writeInt(u32, node, .little);

            // Find edge to next
            const next = cycle[(i + 1) % cycle.len];
            var risk: f64 = 0.0;
            if (graph.getEdge(node, next)) |edge| {
                risk = edge.risk;
            }
            try evidence.writer(allocator).writeAll(std.mem.asBytes(&risk));
        }

        return evidence.toOwnedSlice(allocator);
    }
};

/// Run Bellman-Ford from source, detecting negative cycles (betrayal rings).
///
/// Algorithm:
/// 1. Relax all edges |V|-1 times.
/// 2. On |V|th pass: If any edge still improves → negative cycle exists.
/// 3. Trace cycle via predecessor map.
pub fn detectBetrayal(
    graph: *const RiskGraph,
    source: NodeId,
    allocator: std.mem.Allocator,
) !BellmanFordResult {
    const n = graph.nodeCount();
    if (n == 0) {
        return BellmanFordResult{
            .allocator = allocator,
            .distances = .{},
            .predecessors = .{},
            .betrayal_cycles = .{},
        };
    }

    var dist = std.AutoHashMapUnmanaged(NodeId, f64){};
    var prev = std.AutoHashMapUnmanaged(NodeId, ?NodeId){};

    // Initialize distances
    for (graph.nodes.items) |node| {
        try dist.put(allocator, node, std.math.inf(f64));
        try prev.put(allocator, node, null);
    }
    try dist.put(allocator, source, 0.0);

    // Relax edges |V|-1 times
    for (0..n - 1) |_| {
        var improved = false;

        for (graph.edges.items) |edge| {
            const d_from = dist.get(edge.from) orelse continue;
            if (d_from == std.math.inf(f64)) continue;

            const d_to = dist.get(edge.to) orelse std.math.inf(f64);
            const new_dist = d_from + edge.risk;

            if (new_dist < d_to) {
                try dist.put(allocator, edge.to, new_dist);
                try prev.put(allocator, edge.to, edge.from);
                improved = true;
            }
        }

        if (!improved) break; // Early exit: no more improvements
    }

    // Detect negative cycles (betrayal rings)
    var cycles = std.ArrayListUnmanaged([]NodeId){};
    var in_cycle = std.AutoHashMapUnmanaged(NodeId, bool){};
    defer in_cycle.deinit(allocator);

    for (graph.edges.items) |edge| {
        const d_from = dist.get(edge.from) orelse continue;
        if (d_from == std.math.inf(f64)) continue;

        const d_to = dist.get(edge.to) orelse continue;

        if (d_from + edge.risk < d_to) {
            // Negative cycle detected; trace it
            if (in_cycle.get(edge.to)) |_| continue; // Already traced

            const cycle = try traceCycle(edge.to, &prev, allocator);
            if (cycle.len > 0) {
                for (cycle) |node| {
                    try in_cycle.put(allocator, node, true);
                }
                try cycles.append(allocator, cycle);
            }
        }
    }

    return BellmanFordResult{
        .allocator = allocator,
        .distances = dist,
        .predecessors = prev,
        .betrayal_cycles = cycles,
    };
}

/// Trace a cycle starting from a node in a negative cycle.
fn traceCycle(
    start: NodeId,
    prev: *std.AutoHashMapUnmanaged(NodeId, ?NodeId),
    allocator: std.mem.Allocator,
) ![]NodeId {
    var visited = std.AutoHashMapUnmanaged(NodeId, usize){};
    defer visited.deinit(allocator);

    var path = std.ArrayListUnmanaged(NodeId){};
    defer path.deinit(allocator);

    var current: ?NodeId = start;
    var idx: usize = 0;

    // Walk backward until we hit a repeat (cycle entry)
    while (current) |curr| {
        if (visited.get(curr)) |cycle_start_idx| {
            // Found cycle; extract it
            const cycle_len = idx - cycle_start_idx;
            if (cycle_len == 0) return &[_]NodeId{};

            const cycle = try allocator.alloc(NodeId, cycle_len);
            @memcpy(cycle, path.items[cycle_start_idx..idx]);
            return cycle;
        }

        try visited.put(allocator, curr, idx);
        try path.append(allocator, curr);

        current = if (prev.get(curr)) |p| p else null;
        idx += 1;

        if (idx > 10000) return error.CycleTooLong; // Safety limit
    }

    return &[_]NodeId{}; // No cycle found
}

// ============================================================================
// TESTS
// ============================================================================

test "Bellman-Ford: No betrayal in clean graph" {
    const allocator = std.testing.allocator;
    var graph = RiskGraph.init(allocator);
    defer graph.deinit();

    // A -> B -> C (all positive)
    try graph.addNode(0);
    try graph.addNode(1);
    try graph.addNode(2);

    try graph.addEdge(.{ .from = 0, .to = 1, .risk = 0.5, .timestamp = time.SovereignTimestamp.fromSeconds(0, .system_boot), .nonce = 0, .level = 3, .expires_at = time.SovereignTimestamp.fromSeconds(0, .system_boot) });
    try graph.addEdge(.{ .from = 1, .to = 2, .risk = 0.3, .timestamp = time.SovereignTimestamp.fromSeconds(0, .system_boot), .nonce = 0, .level = 3, .expires_at = time.SovereignTimestamp.fromSeconds(0, .system_boot) });

    var result = try detectBetrayal(&graph, 0, allocator);
    defer result.deinit();

    try std.testing.expectEqual(result.betrayal_cycles.items.len, 0);
    try std.testing.expectEqual(result.computeAnomalyScore(), 0.0);
}

test "Bellman-Ford: Detect negative cycle (betrayal ring)" {
    const allocator = std.testing.allocator;
    var graph = RiskGraph.init(allocator);
    defer graph.deinit();

    // Triangle: A -> B -> C -> A with negative total weight
    // A --0.2-> B --0.2-> C ---(-0.8)--> A = total -0.4 (negative)
    try graph.addNode(0);
    try graph.addNode(1);
    try graph.addNode(2);

    try graph.addEdge(.{ .from = 0, .to = 1, .risk = 0.2, .timestamp = time.SovereignTimestamp.fromSeconds(0, .system_boot), .nonce = 0, .level = 3, .expires_at = time.SovereignTimestamp.fromSeconds(0, .system_boot) });
    try graph.addEdge(.{ .from = 1, .to = 2, .risk = 0.2, .timestamp = time.SovereignTimestamp.fromSeconds(0, .system_boot), .nonce = 0, .level = 3, .expires_at = time.SovereignTimestamp.fromSeconds(0, .system_boot) });
    try graph.addEdge(.{ .from = 2, .to = 0, .risk = -0.8, .timestamp = time.SovereignTimestamp.fromSeconds(0, .system_boot), .nonce = 0, .level = 1, .expires_at = time.SovereignTimestamp.fromSeconds(0, .system_boot) }); // Betrayal!

    var result = try detectBetrayal(&graph, 0, allocator);
    defer result.deinit();

    try std.testing.expectEqual(result.betrayal_cycles.items.len, 1);
    try std.testing.expect(result.computeAnomalyScore() > 0.0);

    // Check evidence generation
    const evidence = try result.generateEvidence(&graph, allocator);
    defer allocator.free(evidence);
    try std.testing.expect(evidence.len > 0);
    try std.testing.expectEqual(evidence[0], 0x01); // Version
}
