
import logging
import hashlib
import time
from typing import Dict, Tuple, Optional, Any

logger = logging.getLogger(__name__)

class ProofVerifier:
    """
    Verifies the semantic validity of Proof of Neural Work.
    
    Unlike the Ledger (which checks signatures and rate limits),
    the Verifier checks the ACTUAL WORK content:
    1. Training: Did the gradients actually reduce loss?
    2. Inference: Did the user actually request and receive this?
    """
    
    def __init__(self, model_interface=None):
        self.model_interface = model_interface
        
    def verify_work_content(self, proof: Any) -> Tuple[bool, str]:
        """
        Verify the content of the work.
        
        Args:
            proof: The proof to verify (PoNWProof object)
            
        Returns:
            (is_valid, reason)
        """
        # Avoid circular import by using string comparison or local import
        # proof_type is a string value from the Enum usually
        
        if proof.proof_type == "training":
            return self._verify_training_work(proof)
        elif proof.proof_type == "inference":
            return self._verify_inference_work(proof)
        elif proof.proof_type == "uptime":
            # Uptime is verified by the ledger's rate limits and gossip checks
            return True, "Uptime valid"
        elif proof.proof_type == "data":
            return True, "Data serving valid (optimistic)"
        else:
            return False, f"Unknown proof type: {proof.proof_type}"
            
    def _verify_training_work(self, proof: Any) -> Tuple[bool, str]:
        """
        Verify training work via Loss-Based Verification.
        
        The "Golden Rule" of PoNW:
        L(w - lr * g) < L(w)
        """
        # 1. Check if proof contains model hash
        if not proof.model_hash:
            return False, "Missing model hash for training proof"
            
        # 2. In a real system, we would:
        #    a) Fetch the gradients (referenced by hash)
        #    b) Load the model state at proof.model_hash
        #    c) Apply gradients and check loss on validation set
        
        # For this implementation, we enforce that training proofs MUST
        # provide a 'gradient_commitment' (which we'll add to the proof structure implicitly via kwargs for now
        # or check if we can access it).
        
        # Critical Security Check: "Lazy Mining" prevention
        # If a node submits a training proof with 0 actual computation time implied, it's fake.
        if proof.training_batches > 0:
            # Heuristic: Minimum time per batch
            # Even on H100, a batch takes > 10ms. 
            min_time_per_batch = 0.01 
            implied_time = proof.uptime_seconds
            if implied_time < (proof.training_batches * min_time_per_batch):
                return False, f"Impossible training speed: {proof.training_batches} batches in {implied_time}s"
                
        return True, "Training work verified (heuristic)"

    def _verify_inference_work(self, proof: Any) -> Tuple[bool, str]:
        """
        Verify inference work via Economic Receipts.
        
        Inference rewards come from User -> Node.
        We verify that the User authorized this work (signed request).
        """
        if not proof.request_id:
            # Legacy/Open inference not supported for secure mining
            return False, "Missing request_id: Anonymous inference not verifiable"
            
        # In a full implementation, we would look up the Request object
        # and verify the user_signature matches.
        # The Ledger does this in _calculate_reward by checking the Market.
        
        return True, "Inference work linked to valid request"
