"""Visible deterministic rules for MKN-028 synthetic context-sharing lab."""
from __future__ import annotations

def inspect_single(f):
    issues=[]
    if not isinstance(f,dict): return ['INPUT_INVALID']
    learner=f.get('learner') or {}; grant=f.get('grant') or {}; scope=f.get('scope') or {}; snap=f.get('snapshot') or {}; ann=f.get('annotation') or {}; access=f.get('access') or {}
    if not grant.get('context_share_id') or not learner.get('learner_ref') or not grant.get('mentor_or_reviewer_ref'): issues.append('IDENTITY_EVIDENCE_MISSING')
    if not grant.get('purpose'): issues.append('PURPOSE_MISSING')
    if not grant.get('authorization_ref'): issues.append('AUTHORIZATION_MISSING')
    if not grant.get('consent_or_basis_ref'): issues.append('BASIS_MISSING')
    approved=scope.get('approved_fields') or []; allowed=set(scope.get('purpose_allowed_fields') or [])
    if any(x not in allowed for x in approved): issues.append('SCOPE_EXCEEDS_PURPOSE')
    rules=scope.get('field_rules') or {}
    for field in approved:
        r=rules.get(field) or {}
        if not r.get('minimum_necessary_reason'): issues.append('MINIMUM_NECESSARY_REASON_MISSING')
        if r.get('redaction_rule')!='INCLUDE': issues.append('APPROVED_FIELD_REDACTION_CONFLICT')
    for field in set(scope.get('requested_fields') or [])-set(approved):
        r=rules.get(field) or {}
        if not str(r.get('redaction_rule') or '').startswith('REDACT'): issues.append('REDACTION_MAP_INCOMPLETE')
    if not snap.get('source_snapshot_hash') or not snap.get('source_record_ref'): issues.append('SNAPSHOT_PROVENANCE_MISSING')
    if ann.get('source_snapshot_hash')!=snap.get('source_snapshot_hash') or not ann.get('stored_separately'): issues.append('ANNOTATION_PROVENANCE_INVALID')
    if learner.get('canonical_write_from_annotations'): issues.append('ANNOTATION_SOURCE_CONTAMINATION')
    if not grant.get('issued_at') or not grant.get('expires_at'): issues.append('GRANT_TIME_BOUNDS_MISSING')
    if grant.get('authorized_expires_at') and grant.get('expires_at')!=grant.get('authorized_expires_at'): issues.append('EXPIRY_NOT_AUTHORIZED')
    if grant.get('onward_share_state')=='ALLOWED' and not grant.get('onward_share_authorization_ref'): issues.append('ONWARD_SHARE_UNAUTHORIZED')
    if not access.get('access_event_id') or not access.get('audit_event_id'): issues.append('ACCESS_AUDIT_MISSING')
    if grant.get('grant_state')=='REVOKED':
        if not grant.get('revoked_at'): issues.append('REVOKE_TIMESTAMP_MISSING')
        if access.get('package_accessible'): issues.append('REVOKED_PACKAGE_ACCESSIBLE')
    return sorted(set(issues))

def expected_single(f):
    issues=inspect_single(f)
    if issues: return 'HOLD'
    grant=f.get('grant') or {}
    if grant.get('grant_state')=='REVOKED': return 'REVOKED'
    if grant.get('grant_state')=='ACTIVE': return 'ACTIVE_BOUNDED'
    return 'REVIEW_REQUIRED'

def inspect_transfer(f):
    issues=[]; grants=f.get('grants') or []
    if len(grants)!=2: issues.append('TRANSFER_GRANT_COUNT_INVALID')
    ids=set()
    for g in grants:
        if not g.get('context_share_id') or g.get('context_share_id') in ids: issues.append('TRANSFER_IDENTITY_INVALID')
        ids.add(g.get('context_share_id'))
        if not g.get('purpose') or not g.get('reviewer_ref') or not g.get('authorization_ref'): issues.append('TRANSFER_AUTHORITY_MISSING')
        if not g.get('source_snapshot_hash') or not g.get('audit_event_id'): issues.append('TRANSFER_PROVENANCE_MISSING')
        if any(x not in set(g.get('purpose_allowed_fields') or []) for x in g.get('scope') or []): issues.append('TRANSFER_SCOPE_EXPANSION')
        if g.get('grant_state')=='REVOKED' and (not g.get('revoked_at') or g.get('package_accessible')): issues.append('TRANSFER_REVOKE_NOT_ENFORCED')
        if g.get('grant_state')=='ACTIVE' and not g.get('package_accessible'): issues.append('TRANSFER_ACTIVE_PACKAGE_UNAVAILABLE')
    hist=f.get('history') or []
    if len(hist)!=2 or not all(x.get('preserved') for x in hist): issues.append('TRANSFER_HISTORY_NOT_PRESERVED')
    return sorted(set(issues))

def expected_transfer(f):
    if inspect_transfer(f): return 'HOLD'
    states={g.get('grant_state') for g in f.get('grants') or []}
    if states=={'ACTIVE','REVOKED'}: return 'MIXED_ACTIVE_REVOKED'
    if states=={'REVOKED'}: return 'REVOKED'
    if states=={'ACTIVE'}: return 'ACTIVE_BOUNDED'
    return 'REVIEW_REQUIRED'
