import json
import os
import re
from typing import Dict, List, Any

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

class OdgsInterceptor:
    def __init__(self, protocol_path: str = None):
        """
        Initialize the Interceptor.
        :param protocol_path: Path to the directory containing ODGS JSON files. 
                              If None, attempts to find them relative to this file.
        """
        if protocol_path:
            self.protocol_path = protocol_path
        else:
            # Default to current directory or package root
            self.protocol_path = os.path.dirname(os.path.abspath(__file__))

        self.graph = self._load_json("ontology_graph.json")
        self.rules = self._load_rules()
        self.metrics = self._load_json("standard_metrics.json")
    
    def _load_json(self, filename: str) -> Dict:
        path = os.path.join(self.protocol_path, filename)
        if not os.path.exists(path):
            raise FileNotFoundError(f"ODGS Protocol file not found: {path}")
        with open(path, 'r') as f:
            return json.load(f)

    def _load_rules(self) -> Dict[str, Dict]:
        """Load rules and index them by URN for fast lookup."""
        rules_data = self._load_json("standard_data_rules.json")
        # Handle list vs dict (rules file might be a list or wrapped in keys)
        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): # Single object or unstructured
             # If it's a single dict (like the example we saw), wrap in list
             rules_list = [rules_data]
        else:
            rules_list = []

        # Index by URN (e.g., urn:odgs:rule:2021)
        # Note: logic assumes rule_id is present.
        indexed = {}
        for rule in rules_list:
             # Construct URN if not present, or use rule_id
             # Standard format: urn:odgs:rule:{id}
             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]) -> bool:
        """
        The Core Logic: Checks if the requested Process Stage is blocked by any Rules
        that fail against the provided Data Context.

        :param process_urn: The URN of the business process stage (e.g., 'urn:odgs:process:O2C_S03')
        :param data_context: Key-value pairs of data being processed (e.g., {'container_id': '...'}).
        :return: True if access granted. Raises ProcessBlockedException if blocked.
        """
        print(f"🛡️  Interceptor Active: Checking Access for Process [{process_urn}]")

        # 1. Identify Blocking Rules from the Graph
        blocking_rules = self._find_blocking_rules(process_urn)
        
        if not blocking_rules:
            print("   ✅ No Hard Stop rules found for this process.")
            return True

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

        # 2. Evaluate each Blocking Rule
        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(rule_def, data_context)
        
        print("   ✅ All Blocking Rules Passed. Access Granted.")
        return True

    def _find_blocking_rules(self, target_process_urn: str) -> List[str]:
        """
        Traverses the Ontology Graph to find edges where:
        Source = Rule, Target = Process, Relationship = BLOCKS_PROCESS
        """
        blockers = []
        # The graph structure in ontology_graph.json usually has "graph_edges" list
        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(self, rule_def: Dict, data: Dict):
        """
        Executes the logic of a specific rule against the data.
        Currently supports: Regex Validation for Container IDs.
        """
        rule_id = rule_def.get("rule_id")
        rule_name = rule_def.get("name")
        print(f"      🔎 Verifying Rule {rule_id}: {rule_name}")

        # --- LOGIC DISPATCHER (Simulation of complex rule engine) ---
        
        # Logic for Rule 2021: ISO 6346 Container ID
        if str(rule_id) == "2021":
            cid = data.get("container_id", "")
            # Regex: 4 letters, 7 numbers (simplified for demo, usually 6 digits + check)
            # The example in file said: [A-Z]{4}[0-9]{7}
            pattern = r"^[A-Z]{4}[0-9]{7}$"
            if not re.match(pattern, str(cid)):
                raise ProcessBlockedException(
                    f"HARD STOP: Rule {rule_id} Failed. "
                    f"Value '{cid}' does not match pattern {pattern}. "
                    f"Process is BLOCKED."
                )
        else:
             print(f"      [info] No executable logic implemented for Rule {rule_id}, passing by default.")

