//! QVL + Proof-of-Path Integration
//!
//! Bridges the existing `proof_of_path.zig` (Phase 3C) with the QVL graph engine.
//! Enables:
//! - Reputation scoring from PoP verification
//! - PoP-guided A* heuristic (prefer paths with proven trust)
//! - Real-time trust decay on PoP failures
//!
//! This is where PoP + Reputation become L1's "magic".

const std = @import("std");
const types = @import("types.zig");
const pathfinding = @import("pathfinding.zig");
const pop = @import("../proof_of_path.zig");
const trust_graph = @import("trust_graph");

const NodeId = types.NodeId;
const RiskGraph = types.RiskGraph;
const RiskEdge = types.RiskEdge;
const ProofOfPath = pop.ProofOfPath;
const PathVerdict = pop.PathVerdict;

/// Reputation score derived from PoP verification.
/// Range: [0.0, 1.0]
/// - 1.0 = Perfect PoP verification history
/// - 0.5 = Neutral (new node, no history)
/// - 0.0 = Consistent PoP failures (likely adversarial)
pub const ReputationScore = struct {
    node: NodeId,
    score: f64,
    /// Total PoP verifications attempted
    total_checks: u32,
    /// Successful verifications
    successful_checks: u32,
    /// Last verified timestamp (entropy stamp)
    last_verified: u64,

    pub fn init(node: NodeId) ReputationScore {
        return .{
            .node = node,
            .score = 0.5, // Neutral default
            .total_checks = 0,
            .successful_checks = 0,
            .last_verified = 0,
        };
    }

    /// Update reputation after a PoP verification attempt.
    pub fn update(self: *ReputationScore, verdict: PathVerdict, entropy_stamp: u64) void {
        self.total_checks += 1;
        if (verdict == .valid) {
            self.successful_checks += 1;
            self.last_verified = entropy_stamp;
        }

        // Bayesian update: score = successful / total (with prior weighting)
        const success_rate = @as(f64, @floatFromInt(self.successful_checks)) /
            @as(f64, @floatFromInt(self.total_checks));

        // Apply damping to prevent extreme swings on single failures
        const damping = 0.7;
        self.score = damping * self.score + (1.0 - damping) * success_rate;

        // Clamp to [0, 1]
        self.score = @max(0.0, @min(1.0, self.score));
    }

    /// Decay reputation over time if no recent verifications.
    pub fn decay(self: *ReputationScore, current_entropy: u64, half_life_ns: u64) void {
        const time_since = current_entropy - self.last_verified;
        if (time_since == 0) return;

        // Exponential decay: score *= 0.5^(time_since / half_life)
        const decay_factor = std.math.pow(
            f64,
            0.5,
            @as(f64, @floatFromInt(time_since)) / @as(f64, @floatFromInt(half_life_ns)),
        );
        self.score *= decay_factor;
        self.score = @max(0.0, self.score);
    }
};

/// Reputation map for all nodes in the graph.
pub const ReputationMap = struct {
    allocator: std.mem.Allocator,
    scores: std.AutoHashMapUnmanaged(NodeId, ReputationScore),
    /// Default half-life: 7 days in nanoseconds
    decay_half_life: u64 = 7 * 24 * 3600 * 1_000_000_000,

    pub fn init(allocator: std.mem.Allocator) ReputationMap {
        return .{
            .allocator = allocator,
            .scores = .{},
        };
    }

    pub fn deinit(self: *ReputationMap) void {
        self.scores.deinit(self.allocator);
    }

    /// Get reputation score for a node (default: 0.5 if unknown).
    pub fn get(self: *const ReputationMap, node: NodeId) f64 {
        if (self.scores.get(node)) |score| {
            return score.score;
        }
        return 0.5; // Neutral for unknown nodes
    }

    /// Record a PoP verification result.
    pub fn recordVerification(
        self: *ReputationMap,
        node: NodeId,
        verdict: PathVerdict,
        entropy_stamp: u64,
    ) !void {
        var entry = try self.scores.getOrPut(self.allocator, node);
        if (!entry.found_existing) {
            entry.value_ptr.* = ReputationScore.init(node);
        }
        entry.value_ptr.update(verdict, entropy_stamp);
    }

    /// Decay all reputations based on current time.
    pub fn applyDecay(self: *ReputationMap, current_entropy: u64) void {
        var it = self.scores.iterator();
        while (it.next()) |entry| {
            entry.value_ptr.decay(current_entropy, self.decay_half_life);
        }
    }

    /// Get all nodes with reputation below threshold.
    pub fn getLowReputationNodes(
        self: *const ReputationMap,
        threshold: f64,
        allocator: std.mem.Allocator,
    ) ![]NodeId {
        var result = std.ArrayListUnmanaged(NodeId){};

        var it = self.scores.iterator();
        while (it.next()) |entry| {
            if (entry.value_ptr.score < threshold) {
                try result.append(allocator, entry.key_ptr.*);
            }
        }

        return result.toOwnedSlice(allocator);
    }
};

/// PoP-aware A* heuristic.
/// Prioritizes paths through high-reputation nodes.
pub fn popReputationHeuristic(
    node: NodeId,
    target: NodeId,
    context: *const anyopaque,
) f64 {
    const rep_map: *const ReputationMap = @ptrCast(@alignCast(context));

    // Base heuristic: assume 1 hop remaining
    const base_cost = 1.0;

    // Reputation penalty: low reputation = higher cost
    const rep = rep_map.get(node);
    const rep_penalty = (1.0 - rep) * 2.0; // Max penalty: 2.0 for rep=0

    _ = target; // Not used in admissible heuristic
    return base_cost + rep_penalty;
}

/// Verify a PoP and update reputation scores.
pub fn verifyAndUpdateReputation(
    proof: *const ProofOfPath,
    expected_receiver: [32]u8,
    expected_sender: [32]u8,
    graph: *const trust_graph.CompactTrustGraph,
    rep_map: *ReputationMap,
    current_entropy: u64,
) PathVerdict {
    const verdict = proof.verify(expected_receiver, expected_sender, graph);

    // Update reputation for the sender
    // (In a full impl, we'd extract NodeId from DID)
    // For now, use a hash of the sender DID as NodeId
    var hasher = std.hash.Wyhash.init(0);
    hasher.update(&expected_sender);
    const sender_id: NodeId = @truncate(hasher.final());

    rep_map.recordVerification(sender_id, verdict, current_entropy) catch {
        // If allocation fails, degrade gracefully (skip reputation update)
    };

    return verdict;
}

/// Initialize RiskGraph edges with reputation-weighted risks.
pub fn populateRiskFromReputation(
    risk_graph: *RiskGraph,
    trust_compact: *const trust_graph.CompactTrustGraph,
    rep_map: *const ReputationMap,
) !void {
    // For each edge in the CompactTrustGraph, add to RiskGraph with risk = (1 - reputation)
    const edges = trust_compact.getAllEdges();

    for (edges) |edge| {
        // Extract NodeIds (would use actual DID->NodeId mapping in full impl)
        var hasher = std.hash.Wyhash.init(0);
        hasher.update(&edge.did);
        const to_id: NodeId = @truncate(hasher.final());

        // Compute risk from reputation
        const rep = rep_map.get(to_id);
        const risk = 1.0 - rep; // High rep = low risk

        const risk_edge = RiskEdge{
            .from = 0, // Would map from trust_compact.root_idx
            .to = to_id,
            .risk = risk,
            .entropy_stamp = 0, // Would extract from edge metadata
            .level = edge.level,
            .expires_at = edge.expires_at orelse 0,
        };

        try risk_graph.addEdge(risk_edge);
    }
}

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

test "ReputationScore: initial neutral score" {
    const score = ReputationScore.init(42);
    try std.testing.expectEqual(score.score, 0.5);
    try std.testing.expectEqual(score.total_checks, 0);
}

test "ReputationScore: successful verifications increase score" {
    var score = ReputationScore.init(42);

    score.update(.valid, 1000);
    try std.testing.expect(score.score > 0.5);

    score.update(.valid, 2000);
    score.update(.valid, 3000);
    try std.testing.expect(score.score > 0.75); // Damping prevents rapid convergence
}

test "ReputationScore: failed verifications decrease score" {
    var score = ReputationScore.init(42);

    score.update(.broken_link, 1000);
    try std.testing.expect(score.score < 0.5);

    score.update(.broken_link, 2000);
    try std.testing.expect(score.score < 0.3);
}

test "ReputationScore: decay over time" {
    var score = ReputationScore.init(42);
    score.update(.valid, 1000);
    const initial = score.score;

    // Decay after half-life
    const half_life: u64 = 1000;
    score.decay(1000 + half_life, half_life);

    try std.testing.expect(score.score < initial);
    try std.testing.expectApproxEqAbs(score.score, initial * 0.5, 0.1);
}

test "ReputationMap: get unknown node" {
    const allocator = std.testing.allocator;
    var rep_map = ReputationMap.init(allocator);
    defer rep_map.deinit();

    const score = rep_map.get(999);
    try std.testing.expectEqual(score, 0.5); // Default neutral
}

test "ReputationMap: record verification" {
    const allocator = std.testing.allocator;
    var rep_map = ReputationMap.init(allocator);
    defer rep_map.deinit();

    try rep_map.recordVerification(42, .valid, 1000);
    const score = rep_map.get(42);
    try std.testing.expect(score > 0.5);
}

test "ReputationMap: low reputation nodes" {
    const allocator = std.testing.allocator;
    var rep_map = ReputationMap.init(allocator);
    defer rep_map.deinit();

    try rep_map.recordVerification(1, .valid, 1000);
    try rep_map.recordVerification(2, .broken_link, 1000);
    try rep_map.recordVerification(2, .broken_link, 2000);

    const low_rep = try rep_map.getLowReputationNodes(0.4, allocator);
    defer allocator.free(low_rep);

    try std.testing.expectEqual(low_rep.len, 1);
    try std.testing.expectEqual(low_rep[0], 2);
}
