#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
## Superego Daemon: Compliance Validator (Phase 3.1)

**Purpose:** To act as the final, non-bypassable gate for all agent-generated outputs, ensuring they comply with externally-defined regulatory and legal frameworks.
"""
import os
import json
import time
import re
import hashlib
from pathlib import Path
import sys
import io

# --- Windows Unicode Fix ---
if os.name == 'nt' and __name__ == '__main__':
    if hasattr(sys.stdout, 'reconfigure'):
        sys.stdout.reconfigure(encoding='utf-8', errors='replace')
    if hasattr(sys.stderr, 'reconfigure'):
        sys.stderr.reconfigure(encoding='utf-8', errors='replace')

# Add Utilities to path
SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent.parent

# When run directly, ensure pulse package is importable
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))

from pulse.core.pulse_crypto import PulseCrypto
from pulse.core.brain_utils import load_json, save_json

# --- Paths ---
FRAMEWORKS_DIR = ROOT_DIR / "Corpus_Callosum" / "Archive"
TITAN_LAWS_FILE = ROOT_DIR / "Corpus_Callosum" / "CONSTITUTION.md"
STATE_DIR = ROOT_DIR / "state"
COMPLETED_DIR = STATE_DIR / "completed"
REJECTED_DIR = STATE_DIR / "rejected"
LOG_FILE = STATE_DIR / "compliance_log.jsonl"
KEY_PATH = ROOT_DIR / "state" / "admin_private_key.pem"

# --- System state files in state/ root — never treated as agent actions ---
SYSTEM_FILES = {
    "task_board.json", "agent_registry.json",
    "daemon_health.json", "pipeline_health.json",
    "brain_health.json", "capture_snapshot.json",
    "daemon_alerts.json", "supervisor_alerts.json",
    "daemon_pids.json", "prediction_alerts.json",
    "pulse_bridge_state.json", "worker_pids.json",
    "homeostatic_state.json", "homeostatic_overrides.json",
    "hebbian_matrix.json", "epistemic_forager_state.json",
    "healer_state.json", "ghost_resonance_state.json",
    "skill_usage.json", "prediction_metrics.json",
    "brain_query_log.json",
    "golden_paths.json",
    "schema_daemon_state.json",
    "procedure_daemon_state.json",
    "drift_detector_state.json",
    "pulse_neural_state.json",
    "search_index.json",
    "user_profile.json",
    "agent_trust.json",
    "consolidation_hashes.json",
    "intent_daemon_state.json",
    "swarm_reflex_state.json",
    "skill_phylogeny.json",
    "skill_synthesizer_state.json",
    "workspace_suppression.jsonl",
    "auto_capture_state.json",
    "rule_embeddings.json",
}

# --- Patterns of Harm (Symposium Distillation) ---
HARM_PATTERNS = {
    "MALICIOUS_EXPLOIT": re.compile(
        r"(?i)(zero-day"
        r"|\bexploit\b(?!\s+(?:the\s+)?(?:potential|opportunit|advantage|possibilit|feature|capabilit))"
        r"|buffer overflow|sql injection|\brce\b|privilege escalation)"
    ),
    "FRAUD_HARM": re.compile(r"(?i)(phishing|social engineering|scam|fraud|identity theft|credit card)"),
    "SYSTEMIC_OPPRESSION": re.compile(r"(?i)(apartheid|segregation|supremacy|hate speech|incitement)"),
    "LETHAL_FORCE": re.compile(
        r"(?i)(\bweapon\b(?!\s+of\s+choice)"
        r"|(?<!time )(?<!logic )(?<!fork )\bbomb\b"
        r"|explosive|lethal|assassination)"
    ),
}

# --- PII Detection Patterns (V2 — Expanded for HIPAA/GDPR/PCI) ---
PII_PATTERNS = {
    "EMAIL": re.compile(r'[\w\.-]+@[\w\.-]+\.\w{2,}'),
    "PHONE": re.compile(r'(?<![sS]ection )(?<!\d)(?<![-:T])\b\d{3}[-.\s]\d{3}[-.\s]\d{4}\b(?!\d|T|[-:])'),
    "SSN": re.compile(r'\b\d{3}-\d{2}-\d{4}\b'),
    "CREDIT_CARD": re.compile(r'\b(?:4[0-9]|5[1-5]|3[47]|6[05])(?:\d[ -]*?){11,17}\b'),
    "IP_ADDRESS": re.compile(r'(?<![vV]ersion )\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b'),
    "PASSPORT": re.compile(r'\b[A-Z]{1,2}\d{6,9}\b'),
}

class ComplianceValidator:
    def __init__(self, private_key_path):
        self.rules = {}
        self.private_key_path = private_key_path
        self._ensure_dirs()

    def _ensure_dirs(self):
        COMPLETED_DIR.mkdir(parents=True, exist_ok=True)
        REJECTED_DIR.mkdir(parents=True, exist_ok=True)

    def log_event(self, event_data):
        with open(LOG_FILE, 'a', encoding="utf-8") as f:
            f.write(json.dumps(event_data) + '\n')

    def load_rules(self):
        print("[ComplianceValidator] Loading regulatory frameworks...")
        for rule_file in FRAMEWORKS_DIR.glob("*.json"):
            try:
                framework_name = rule_file.stem
                self.rules[framework_name] = load_json(rule_file)
                print(f"  - Loaded framework: {framework_name}")
            except Exception as e:
                print(f"  - ERROR loading {rule_file}: {e}")
        return self

    def validate(self, action_data):
        print(f"[ComplianceValidator] Validating action: {action_data.get('id', 'N/A')}")
        violations = []
        content_to_check = json.dumps(action_data.get("output", {}))
        
        # 1. Titan Law Check (Phase 3.5)
        for harm_type, pattern in HARM_PATTERNS.items():
            if pattern.search(content_to_check):
                violations.append(f"TITAN_VIOLATION:{harm_type}")

        # 2. V1 Check: PII Detection
        pii_rule = self.rules.get("pii_detection", {})
        if pii_rule.get("enabled", False):
            for pii_type, pattern in PII_PATTERNS.items():
                if pattern.search(content_to_check):
                    violations.append(f"PII_DETECTED:{pii_type}")

        print(f"  - Violations found: {len(violations)}")
        return violations

    def get_socratic_refusal(self, violations):
        """// Generates a corrective response based on Titan Laws."""
        reasons = [v.split(":")[1] for v in violations if "TITAN" in v]
        return (
            f"REFUSAL: This request mirrors a pattern of harm ({', '.join(reasons)}) "
            f"identified in the TITAN_LAWS. How does this serve the principle of Non-Harm?"
        )

    def generate_poc(self, action_data, violations):
        action_hash = hashlib.sha256(json.dumps(action_data, sort_keys=True).encode()).hexdigest()
        
        poc_data = {
            "timestamp": time.time(),
            "action_id": action_data.get("id"),
            "action_hash": action_hash,
            "status": "REJECTED" if violations else "APPROVED",
            "violations": violations,
            "ruleset_versions": {name: rules.get("version", "N/A") for name, rules in self.rules.items()}
        }
        
        signature = PulseCrypto.sign_with_rsa(json.dumps(poc_data).encode(), self.private_key_path)
        
        return {
            "certificate": poc_data,
            "signature": signature.hex()
        }

    def watch(self):
        print("[ComplianceValidator] Watching Action Queue for new actions...")
        self.crypto = PulseCrypto()  # Initialize crypto for agent verification
        # S40: track consecutive failures per file to quarantine persistent bad files
        _failure_counts: dict = {}
        QUARANTINE_DIR = STATE_DIR / "quarantine"
        QUARANTINE_THRESHOLD = 3
        while True:
            try:
                for action_file in STATE_DIR.glob("*.json"):
                    if not action_file.is_file() or action_file.name.endswith(".sig"):
                        continue
                    # Skip system files — these are NOT agent actions
                    if action_file.name in SYSTEM_FILES:
                        continue

                    sig_file = action_file.with_suffix(action_file.suffix + ".sig")

                    try:
                        print(f"--- New Action Detected: {action_file.name} ---")

                        # 1. Identity Verification (Phase 3.3 Task B)
                        if not sig_file.exists():
                            print(f"[Validator] REJECTED: Missing signature for {action_file.name}")
                            action_file.unlink()
                            _failure_counts.pop(action_file.name, None)
                            continue

                        action_json_str = action_file.read_text(encoding="utf-8")
                        signature = sig_file.read_text(encoding="utf-8").strip()
                        action_data = json.loads(action_json_str)
                        agent_id = action_data.get("agent_id")

                        if not agent_id or not self.crypto.verify_string(action_json_str, signature, agent_id):
                            print(f"[Validator] REJECTED: INVALID IDENTITY for {action_file.name}")
                            action_file.unlink()
                            sig_file.unlink()
                            _failure_counts.pop(action_file.name, None)
                            continue

                        # 2. Compliance Validation
                        violations = self.validate(action_data)
                        poc = self.generate_poc(action_data, violations)

                        if violations:
                            destination = REJECTED_DIR / action_file.name
                            action_data["validation_status"] = "rejected"
                            action_data["proof_of_compliance"] = poc
                            # Trigger Socratic Refusal if Titan Law violated
                            if any("TITAN" in v for v in violations):
                                action_data["refusal_dialogue"] = self.get_socratic_refusal(violations)
                            save_json(destination, action_data)
                        else:
                            destination = COMPLETED_DIR / action_file.name
                            action_data["validation_status"] = "approved"
                            action_data["proof_of_compliance"] = poc
                            save_json(destination, action_data)

                        self.log_event(poc)
                        action_file.unlink()
                        sig_file.unlink()
                        _failure_counts.pop(action_file.name, None)
                        print(f"--- Action Processed. Moved to: {destination.parent.name} ---")

                    except Exception as e:
                        print(f"[ERROR] Could not process {action_file}: {e}")
                        # S40: quarantine after QUARANTINE_THRESHOLD consecutive failures
                        fname = action_file.name
                        _failure_counts[fname] = _failure_counts.get(fname, 0) + 1
                        if _failure_counts[fname] >= QUARANTINE_THRESHOLD:
                            try:
                                QUARANTINE_DIR.mkdir(parents=True, exist_ok=True)
                                action_file.replace(QUARANTINE_DIR / fname)
                                if sig_file.exists():
                                    sig_file.replace(QUARANTINE_DIR / sig_file.name)
                                print(f"[Validator] QUARANTINED {fname} after {QUARANTINE_THRESHOLD} failures")
                            except Exception as qe:
                                print(f"[Validator] Quarantine failed for {fname}: {qe}")
                            _failure_counts.pop(fname, None)
            finally:
                # S40: sleep in finally block — always runs, prevents tight spin
                time.sleep(2)

if __name__ == "__main__":

    # This key is used for signing the PoC, not for env sealing.

    # In a real system, this would be a separate, less privileged key.

    if not KEY_PATH.exists():

        print(f"CRITICAL: Validator's private key not found at {KEY_PATH}")

        sys.exit(1)


    validator = ComplianceValidator(private_key_path=KEY_PATH).load_rules()

    validator.watch()
