import json
import os
import re
import sys
import logging
import datetime
import hashlib
import uuid
from typing import Dict, List, Any
try:
    from simpleeval import simple_eval, NameNotDefined
except ImportError:
    # Fallback/Mock for environment without simpleeval
    def simple_eval(expr, names):
        return True
    class NameNotDefined(Exception): pass

# Add project root to sys.path
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(current_dir)
if project_root not in sys.path:
    sys.path.append(project_root)

from odgs.system.scripts.hashing import generate_project_hash
from odgs.system.adapters.git_log_adapter import GitAuditLogger

# --- LOGGING SETUP ---
audit_logger = logging.getLogger("sovereign_audit")
audit_logger.setLevel(logging.INFO)
# Avoid adding duplicates
if not audit_logger.handlers:
    handler = logging.FileHandler(os.path.join(project_root, "sovereign_audit.log"))
    handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
    audit_logger.addHandler(handler)

# --- GIT LOGGER ---
git_logger = GitAuditLogger(project_root)

# --- DYNAMIC EVALUATION HELPERS ---
def regex_match(pattern, value):
    if value is None: return False
    try:
        return bool(re.match(pattern, str(value)))
    except:
        return False

def parse_date(value):
    if not value: return datetime.datetime.min
    try:
        # Handle 'YYYY-MM-DD' and simple ISO
        s = str(value)[:10]
        return datetime.datetime.strptime(s, "%Y-%m-%d")
    except:
        return datetime.datetime.min

def today():
    return datetime.datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)

SAFE_FUNCTIONS = {
    "regex_match": regex_match,
    "parse_date": parse_date,
    "today": today
}

class ProcessBlockedException(Exception):
    """Raised when a Process is blocked by a Rule Violation (Hard Stop)."""
    pass

class SecurityException(Exception):
    """Raised when a Cryptographic Handshake failure occurs."""
    pass

class OdgsInterceptor:
    def __init__(self, project_root_path: str = None):
        """
        Initialize the Sovereign Interceptor.
        """
        if project_root_path:
            self.project_root = project_root_path
        else:
            self.project_root = os.path.dirname(os.path.abspath(__file__))
            if self.project_root.endswith("executive"):
                self.project_root = os.path.dirname(self.project_root)

        self.graph = self._load_from_plane("legislative", "ontology_graph.json")
        self.rules = self._load_rules()
        self.metrics = self._load_from_plane("legislative", "standard_metrics.json")
    
    def _load_from_plane(self, plane: str, filename: str) -> Dict:
        path = os.path.join(self.project_root, plane, filename)
        if not os.path.exists(path):
            # In a real scenario, this might crash, but for resilience we log error
            print(f"CRITICAL: Sovereign Artifact missing: {path}")
            return {}
        with open(path, 'r') as f:
            return json.load(f)

    def _load_rules(self) -> Dict[str, Dict]:
        """Load rules from Judiciary Plane and index them by URN."""
        rules_data = self._load_from_plane("judiciary", "standard_data_rules.json")
        
        if isinstance(rules_data, dict) and "rules" in rules_data:
            rules_list = rules_data["rules"]
        elif isinstance(rules_data, list):
            rules_list = rules_data
        elif isinstance(rules_data, dict):
            rules_list = [rules_data]
        else:
            rules_list = []

        indexed = {}
        for rule in rules_list:
             rid = str(rule.get("rule_id", ""))
             urn = f"urn:odgs:rule:{rid}"
             indexed[urn] = rule
        return indexed

    def intercept(self, process_urn: str, data_context: Dict[str, Any], required_integrity_hash: str) -> bool:
        """
        The Core Logic: Checks if the requested Process Stage is blocked by any Rules.
        
        SOVEREIGN PROTOCOL ENFORCED:
        1. Tri-Partite Binding (Input + Def + Config)
        2. Dynamic Rule Evaluation (Configuration as Law)
        3. Git-as-Backend Logging (Data Sovereignty)
        """
        
        # 1. GENERATE INPUT HASH (The "What")
        # Canonicalize JSON to ensure deterministic hash
        try:
            canonical_input = json.dumps(data_context, sort_keys=True, separators=(',', ':'))
            input_hash = hashlib.sha256(canonical_input.encode('utf-8')).hexdigest()
        except:
            input_hash = "HASH_ERROR_NON_SERIALIZABLE"

        # 2. PREPARE AUDIT ENTRY (Article 12 Schema)
        event_id = str(uuid.uuid4())
        audit_entry = {
            "event_id": event_id,
            "timestamp": datetime.datetime.utcnow().isoformat() + "Z",
            "process_urn": process_urn,
            "outcome": "PENDING",
            "evidence": {
                "input_payload_hash": input_hash,
                "active_definition_hash": required_integrity_hash, # Expected state (The "Why")
                "configuration_hash": "SHA256(config_v1)" # Placeholder for v2.0
            },
            "compliance_ref": "EU-AI-ACT-ART-12"
        }

        print(f"🛡️  Interceptor Active: Checking Access for Process [{process_urn}]")
        print(f"   🧾 Event ID: {event_id}")

        try:
            # 3. SOVEREIGN HANDSHAKE (Cryptographic Verification)
            print(f"   🔐 Handshake Requested. Verifying Integrity...")
            current_state = generate_project_hash(self.project_root)
            current_hash = current_state["master_hash"]
            
            # Record the ACTUAL state we are enforcing against
            audit_entry["evidence"]["actual_definition_hash"] = current_hash

            if required_integrity_hash and current_hash != required_integrity_hash:
                audit_entry["outcome"] = "HARD_STOP"
                audit_entry["reason"] = "INTEGRITY_MISMATCH"
                self._commit_log(audit_entry)
                
                error_msg = (
                    f"CRITICAL SECURITY FAILURE: Governance Hash Mismatch.\n"
                    f"Expected: {required_integrity_hash}\n"
                    f"Actual:   {current_hash}\n"
                    f"The Semantic Laws have been altered or do not match the signature."
                )
                print(f"   ⛔ {error_msg}")
                raise SecurityException(error_msg)
            else:
                print(f"   ✅ Integrity Verified.")

            # 4. IDENTIFY BLOCKING RULES
            blocking_rules = self._find_blocking_rules(process_urn)
            
            if not blocking_rules:
                print("   ✅ No Hard Stop rules found for this process.")
                audit_entry["outcome"] = "GRANTED"
                self._commit_log(audit_entry)
                return True

            print(f"   ⚠️  Found {len(blocking_rules)} Potential Blocking Rules.")

            # 5. DYNAMIC EVALUATION
            for rule_urn in blocking_rules:
                rule_def = self.rules.get(rule_urn)
                if not rule_def:
                    print(f"   [warn] Rule definition not found for {rule_urn}, skipping.")
                    continue
                
                self._evaluate_rule_dynamic(rule_def, data_context)
            
            print("   ✅ All Blocking Rules Passed. Access Granted.")
            audit_entry["outcome"] = "GRANTED"
            self._commit_log(audit_entry)
            return True

        except ProcessBlockedException as e:
            # 400 Bad Request equivalent
            audit_entry["outcome"] = "HARD_STOP"
            audit_entry["reason"] = "RULE_VIOLATION"
            audit_entry["details"] = str(e)
            self._commit_log(audit_entry)
            raise e
            
        except SecurityException as e:
            # 403 Forbidden equivalent (already logged above but ensuring catch)
            raise e

        except Exception as e:
            # 500 equivalent
            audit_entry["outcome"] = "SYSTEM_ERROR"
            audit_entry["reason"] = type(e).__name__
            audit_entry["details"] = str(e)
            self._commit_log(audit_entry)
            raise e

    def _commit_log(self, entry: Dict):
        """
        Writes to standard log AND Git-backed Sovereign log.
        """
        # 1. Standard Logging (Legacy/Backup)
        audit_logger.info(json.dumps(entry))
        
        # 2. Sovereign Git Logging (Article 12 Durable Medium)
        git_hash = git_logger.write_entry(entry)
        if git_hash:
             print(f"   📝 Evidence Secured: Commit {git_hash[:7]}")

    def _find_blocking_rules(self, target_process_urn: str) -> List[str]:
        blockers = []
        edges = self.graph.get("graph_edges", [])
        for edge in edges:
            if (edge.get("target_urn") == target_process_urn and 
                edge.get("relationship") == "BLOCKS_PROCESS"):
                blockers.append(edge.get("source_urn"))
        return blockers

    def _evaluate_rule_dynamic(self, rule_def: Dict, data: Dict):
        rule_id = rule_def.get("rule_id")
        rule_name = rule_def.get("name")
        logic = rule_def.get("logic_expression")
        
        print(f"      🔎 Verifying Rule {rule_id}: {rule_name}")

        # Fallback for legacy ID-based logic if no expression exists
        # This maintains backward compatibility until all rules have logic_expression
        if not logic:
            if str(rule_id) == "2021":
                # Legacy Hardcode for Container ID
                cid = data.get("container_id", data.get("value", ""))
                pattern = r"^[A-Z]{4}[0-9]{7}$"
                if not re.match(pattern, str(cid)):
                     raise ProcessBlockedException(f"HARD STOP: Rule {rule_id} Failed (Legacy Check).")
            return

        # Prepare Evaluation Context
        # We allow rule logic to access:
        # 1. 'value' (Generic target)
        # 2. Specific field names (e.g. 'price', 'temperature')
        # 3. Helper functions (regex_match, today, etc.)
        
        eval_context = SAFE_FUNCTIONS.copy()
        eval_context.update(data)
        
        # Heuristic: If "value" isn't explicitly in data, but data has only 1 item, treat it as value
        if "value" not in data and len(data) == 1:
            eval_context["value"] = list(data.values())[0]

        try:
             # Evaluate Expression
             # logic string example: "value > 0" or "regex_match(r'...', value)"
             # We must pass functions separately for simpleeval to use them in calls
             is_valid = simple_eval(logic, names=eval_context, functions=SAFE_FUNCTIONS)
                 
        except NameNotDefined as e:
            # Missing data field required by rule
            print(f"      [warn] Rule {rule_id} missing data: {e}")
            raise ProcessBlockedException(f"HARD STOP: Rule {rule_id} Failed. Missing required data field: {e}")
            
        except Exception as e:
             print(f"      [error] Rule {rule_id} logic error: {e}")
             raise ProcessBlockedException(f"HARD STOP: Rule {rule_id} Execution Failed: {e}")
        
        if not is_valid:
             raise ProcessBlockedException(
                 f"HARD STOP: Rule {rule_id} Failed. "
                 f"Expression '{logic}' evaluated to False against provided data."
             )
