"""
MCP Tool 889: PROOF

Generate cryptographic proof that verdict is correct before writing to memory.

Constitutional validation:
- F2 (Truth): Proves verdict is not hallucinated
- F4 (Clarity): Proof is transparent and verifiable

This tool creates a Merkle tree proof of the verdict chain.
Always PASS (never blocks).
"""

import asyncio
import hashlib
from datetime import datetime, timezone
from typing import Any, Dict, List

from arifos.mcp.models import VerdictResponse


def generate_proof_hash(verdict_chain: List[str], decision_tree: Dict[str, Any]) -> str:
    """
    Create Merkle tree from verdict chain and return root hash.

    Args:
        verdict_chain: List of verdict strings ["222:PASS", "444:PASS", ...]
        decision_tree: Dict of tool verdicts and metadata

    Returns:
        SHA-256 hash of Merkle root (64-character hex string)
    """
    if not verdict_chain:
        # Empty chain → hash empty string
        return hashlib.sha256(b"").hexdigest()

    # Hash each verdict entry
    hashed_nodes = [hashlib.sha256(v.encode('utf-8')).hexdigest() for v in verdict_chain]

    # Build Merkle tree
    tree = build_merkle_tree(hashed_nodes)

    # Root is the last level, last element
    if tree:
        return tree[-1][0]
    else:
        return hashlib.sha256(b"").hexdigest()


def build_merkle_tree(nodes: List[str]) -> List[List[str]]:
    """
    Build Merkle tree from list of hashes.

    Algorithm:
    - Level 0: Hash each node (already hashed externally)
    - Level N+1: Pair nodes from level N, hash pairs
    - If odd nodes: duplicate last node before pairing
    - Continue until single root node

    Args:
        nodes: List of SHA-256 hashes (hex strings)

    Returns:
        List of tree levels, root at end
    """
    if not nodes:
        return []

    tree = [nodes]

    # Build tree bottom-up
    while len(tree[-1]) > 1:
        current_level = tree[-1]
        next_level = []

        # Pair nodes and hash
        for i in range(0, len(current_level), 2):
            left = current_level[i]

            # If odd number of nodes, duplicate last node
            if i + 1 < len(current_level):
                right = current_level[i + 1]
            else:
                right = current_level[i]

            # Hash pair (sort for consistency with validation)
            if left <= right:
                combined = left + right
            else:
                combined = right + left
            pair_hash = hashlib.sha256(combined.encode('utf-8')).hexdigest()
            next_level.append(pair_hash)

        tree.append(next_level)

    return tree


def generate_merkle_path(tree: List[List[str]], leaf_index: int = 0) -> List[str]:
    """
    Generate Merkle path from leaf to root.

    Args:
        tree: Merkle tree (list of levels)
        leaf_index: Index of leaf node

    Returns:
        List of hashes representing path from leaf to root
    """
    if not tree or not tree[0]:
        return []

    path = []
    current_index = leaf_index

    # Traverse from leaves to root
    for level_idx in range(len(tree) - 1):
        current_level = tree[level_idx]

        # Find sibling
        if current_index % 2 == 0:
            # Left node, sibling is right
            sibling_index = current_index + 1
        else:
            # Right node, sibling is left
            sibling_index = current_index - 1

        # Add sibling to path (if exists)
        if sibling_index < len(current_level):
            path.append(current_level[sibling_index])
        else:
            # No sibling (odd number), duplicate current
            path.append(current_level[current_index])

        # Move to parent index
        current_index = current_index // 2

    return path


def validate_merkle_proof(leaf: str, path: List[str], root: str) -> bool:
    """
    Verify that leaf + path can be hashed to reach root.

    Args:
        leaf: Starting leaf hash
        path: Merkle path elements
        root: Expected root hash

    Returns:
        True if computed root matches provided root
    """
    if not path:
        # No path → leaf should equal root
        return leaf == root

    current_hash = leaf

    # Traverse path, combining hashes
    for sibling in path:
        # Combine current with sibling (order matters)
        # For simplicity, always concatenate in sorted order
        if current_hash <= sibling:
            combined = current_hash + sibling
        else:
            combined = sibling + current_hash

        # Hash the combination
        current_hash = hashlib.sha256(combined.encode('utf-8')).hexdigest()

    return current_hash == root


async def mcp_889_proof(request: Dict[str, Any]) -> VerdictResponse:
    """
    MCP Tool 889: PROOF - Generate cryptographic proof.

    Always PASS (proof generation, not rejection).

    Args:
        request: {
            "verdict_chain": ["222:PASS", "444:PASS", ...],
            "decision_tree": {
                "222": {...side_data...},
                "444": {...side_data...},
                ...
            },
            "claim": "Response text"
        }

    Returns:
        VerdictResponse with proof_hash, merkle_path, etc. in side_data
    """
    # Extract inputs
    verdict_chain = request.get("verdict_chain", [])
    decision_tree = request.get("decision_tree", {})
    claim = request.get("claim", "")

    # Validate inputs
    if not isinstance(verdict_chain, list):
        verdict_chain = []
    if not isinstance(decision_tree, dict):
        decision_tree = {}
    if not isinstance(claim, str):
        claim = ""

    # Generate proof hash
    proof_hash = generate_proof_hash(verdict_chain, decision_tree)

    # Build Merkle tree
    if verdict_chain:
        hashed_nodes = [hashlib.sha256(v.encode('utf-8')).hexdigest() for v in verdict_chain]
        tree = build_merkle_tree(hashed_nodes)

        # Generate Merkle path from first entry
        merkle_path = generate_merkle_path(tree, leaf_index=0)

        # Validate proof (should be valid by construction)
        if hashed_nodes:
            proof_valid = validate_merkle_proof(hashed_nodes[0], merkle_path, proof_hash)
        else:
            proof_valid = True
    else:
        merkle_path = []
        proof_valid = True

    # Count nodes verified
    nodes_verified = len(verdict_chain)

    # Generate timestamp
    timestamp = datetime.now(timezone.utc).isoformat()

    return VerdictResponse(
        verdict="PASS",
        reason="Cryptographic proof generated successfully",
        side_data={
            "proof_hash": proof_hash,
            "merkle_path": merkle_path,
            "proof_valid": proof_valid,
            "timestamp": timestamp,
            "nodes_verified": nodes_verified
        },
        timestamp=timestamp
    )


def mcp_889_proof_sync(request: Dict[str, Any]) -> VerdictResponse:
    """Synchronous wrapper for mcp_889_proof."""
    return asyncio.run(mcp_889_proof(request))
