#!/usr/bin/env python3
"""
sat_gate.py - Morph-SAT op satisfiability gate

Enforces that only ops listed in the Morph-SAT matrix can be invoked.
Fail-closed: unknown ops raise RuntimeError.

Usage:
  from flowtorch.sat_gate import SatGate

  SAT = SatGate("morph_sat/matrix.v1.json")
  SAT.allow("conv1d_b64")  # OK
  SAT.allow("forbidden_op")  # Raises RuntimeError
"""
import json, sys
from pathlib import Path

class SatGate:
    """Runtime gate for compressed-domain op satisfiability"""

    def __init__(self, matrix_path: str):
        """Load Morph-SAT matrix and build allowed-op set"""
        self.matrix_path = Path(matrix_path)
        if not self.matrix_path.exists():
            raise FileNotFoundError(f"Morph-SAT matrix not found: {matrix_path}")

        self.mat = json.loads(self.matrix_path.read_text())

        # Validate schema
        if self.mat.get("schema") != "morph-sat/1.0":
            raise ValueError(f"Invalid Morph-SAT schema: {self.mat.get('schema')}")

        # Build allowed-op set
        self.allowed = {op["name"] for op in self.mat["ops"]}
        self.ops_dict = {op["name"]: op for op in self.mat["ops"]}

        # Cache constraints
        self.lossless = self.mat["constraints"]["lossless"]
        self.deterministic = self.mat["constraints"]["deterministic"]

    def allow(self, op_name: str) -> bool:
        """
        Check if op is allowed by Morph-SAT.

        Raises:
          RuntimeError: If op not in allowed set (fail-closed)

        Returns:
          True if allowed
        """
        if op_name not in self.allowed:
            raise RuntimeError(
                f"MORPH-SAT: op '{op_name}' not in satisfiability matrix "
                f"(allowed: {sorted(self.allowed)})"
            )
        return True

    def get_op(self, op_name: str) -> dict:
        """Get full op spec from matrix (pre/post conditions, proofs)"""
        self.allow(op_name)  # Validates first
        return self.ops_dict[op_name]

    def list_ops(self) -> list:
        """List all allowed op names"""
        return sorted(self.allowed)


if __name__ == "__main__":
    # CLI test
    if len(sys.argv) < 3:
        print("Usage: sat_gate.py <matrix.json> <op_name>", file=sys.stderr)
        sys.exit(1)

    gate = SatGate(sys.argv[1])
    op = sys.argv[2]

    try:
        gate.allow(op)
        spec = gate.get_op(op)
        print(f"✅ Op '{op}' allowed")
        print(f"   Signature: {spec['signature']}")
        print(f"   Pre: {spec['pre']}")
        print(f"   Post: {spec['post']}")
        print(f"   Proofs: {spec['proofs']}")
    except RuntimeError as e:
        print(f"❌ {e}")
        sys.exit(1)
