from __future__ import annotations
from datetime import datetime, timezone
REQUIRED=['exception_id','requester_id','owner_authority_ref','normal_rule_ref','requested_action','subject_scope','resource_scope','reason_code','evidence_refs','risk_class','approval_required','decision_state','decision_by','decision_at','effective_from','expires_at','compensating_controls','audit_event_ref','revocation_trigger','revalidation_due','policy_change_candidate','final_disposition']

def _blank(v): return v is None or (isinstance(v,str) and not v.strip())
def _dt(s):
    if not s or str(s).upper() in {'NONE','UNKNOWN'}: return None
    return datetime.fromisoformat(str(s).replace('Z','+00:00'))

def evaluate(r):
    for k in REQUIRED:
        if k not in r or _blank(r[k]): return 'UNKNOWN_DECISIVE_EVIDENCE'
    if r.get('transfer_case') and r.get('baseline_approval_reused'): return 'HOLD_RECOMPUTE_REQUIRED'
    if str(r.get('owner_authority_ref','')).upper() in {'UNKNOWN','NONE','UNVERIFIED','DISPUTED','STALE'}: return 'HOLD_AUTHORITY_UNVERIFIED'
    if r.get('authority_applicable') is False: return 'HOLD_AUTHORITY_NOT_APPLICABLE'
    if r.get('scope_broadened') or r.get('subject_scope_authorized') is False or r.get('resource_scope_authorized') is False or r.get('action_scope_authorized') is False: return 'HOLD_SCOPE_BROADENED'
    if r.get('revocation_trigger_active'): return 'BLOCKED_EXCEPTION_REVOKED'
    as_of=_dt(r.get('as_of')) or datetime(2026,9,14,18,15,tzinfo=timezone.utc)
    exp=_dt(r.get('expires_at'))
    if exp and as_of>exp and r.get('exception_executable',True): return 'BLOCKED_EXCEPTION_EXPIRED'
    if bool(r.get('approval_required')) and str(r.get('decision_by','')).upper() in {'NONE','UNKNOWN','UNAUTHORIZED'}: return 'HOLD_APPROVAL_REQUIRED'
    if str(r.get('risk_class','')).upper().startswith('HIGH') and str(r.get('compensating_controls','')).upper() in {'NONE','UNKNOWN'}: return 'HOLD_CONTROL_REQUIRED'
    if str(r.get('audit_event_ref','')).upper() in {'NONE','UNKNOWN'}: return 'HOLD_AUDIT_REQUIRED'
    if r.get('repeated_exception') and not r.get('policy_change_candidate'): return 'HOLD_POLICY_REVIEW_REQUIRED'
    if r.get('repeated_exception') and str(r.get('final_disposition','')).upper() in {'CONTINUE_AS_NORMAL','STANDING_EXCEPTION'}: return 'HOLD_POLICY_REVIEW_REQUIRED'
    if str(r.get('decision_state','')).upper() in {'REJECT','BLOCKED','REVOKED','EXPIRED'}: return str(r.get('decision_state')).upper()
    return 'APPROVE_BOUNDED'

def check_case(case):
    got=evaluate(case['record']); return got, got==case['expected']

def invariant_checks(case):
    r=case['record']; got=evaluate(r)
    return [
      got==case['expected'],
      bool(str(r.get('normal_rule_ref','')).strip()),
      bool(str(r.get('exception_id','')).strip()),
      not (got=='APPROVE_BOUNDED' and str(r.get('owner_authority_ref','')).upper() in {'UNKNOWN','NONE','UNVERIFIED','DISPUTED','STALE'}),
      not (got=='APPROVE_BOUNDED' and (r.get('scope_broadened') or r.get('revocation_trigger_active'))),
      not (got=='APPROVE_BOUNDED' and r.get('transfer_case') and r.get('baseline_approval_reused')),
    ]
