from datetime import datetime
MISSING={None,"","MISSING","UNKNOWN","N/A"}
REQ=['asset_id', 'asset_type', 'source_uri', 'creator_id', 'rightsholder_id', 'authorship_evidence_ref', 'license_id', 'permission_ref', 'permitted_use', 'prohibited_use', 'commercial_use_state', 'derivative_use_state', 'training_use_state', 'redistribution_state', 'attribution_requirement', 'term_end', 'provenance_ref', 'content_credential_ref', 'rights_conflict_state', 'rights_review_required', 'decision_state', 'audit_ref']
BOOL_AUX=['license_active','attribution_satisfied','permission_scope_verified','content_credential_valid','term_recomputed','transfer_recomputed','baseline_decision_reused','baseline_permission_reused']
OTHER_AUX=['intended_use','provenance_integrity_state','human_contribution_state','legal_review_state','transfer_variant']
def missing(v): return v in MISSING
def date(v):
    try:return datetime.fromisoformat(v)
    except:return None
def uses(v): return {x.strip() for x in str(v).split('|') if x.strip()}
def evaluate(x, as_of='2026-09-14'):
    for f in REQ:
        if missing(x.get(f)): return 'HOLD_REQUIRED_EVIDENCE_MISSING:'+f
    for f in BOOL_AUX:
        if f not in x: return 'HOLD_AUX_EVIDENCE_MISSING:'+f
    for f in OTHER_AUX:
        if missing(x.get(f)): return 'HOLD_AUX_EVIDENCE_MISSING:'+f
    # identity / source do not create permission
    if x.get('source_uri','').startswith(('http://','https://')) and x.get('permission_ref') in {'NONE','NO_PERMISSION','UNKNOWN'}:
        return 'HOLD_PERMISSION_MISSING'
    if x.get('permission_ref') in {'NONE','NO_PERMISSION','UNKNOWN'} or x.get('license_id') in {'NONE','NO_LICENSE','UNKNOWN'}:
        if x.get('content_credential_valid') is True or x.get('provenance_integrity_state')=='VERIFIED':
            return 'HOLD_PROVENANCE_NOT_PERMISSION'
        return 'HOLD_PERMISSION_MISSING'
    if x.get('permission_scope_verified') is not True: return 'HOLD_PERMISSION_SCOPE_UNVERIFIED'
    # term must be active and recomputed
    td=date(x.get('term_end')); ad=date(as_of)
    if td is None: return 'HOLD_TERM_INVALID'
    if x.get('term_recomputed') is not True: return 'HOLD_TERM_NOT_RECOMPUTED'
    if x.get('license_active') is not True or td < ad:
        return 'HOLD_LICENSE_EXPIRED'
    # intended-use specific permission
    intended=x.get('intended_use'); allowed=uses(x.get('permitted_use')); prohibited=uses(x.get('prohibited_use'))
    if intended in prohibited: return 'HOLD_USE_EXPLICITLY_PROHIBITED'
    if intended not in allowed: return 'HOLD_USE_OUTSIDE_PERMISSION'
    # specific state contradictions
    use_state_map={'COMMERCIAL':'commercial_use_state','DERIVATIVE':'derivative_use_state','MODEL_TRAINING':'training_use_state','REDISTRIBUTION':'redistribution_state'}
    for use,field in use_state_map.items():
        state=x.get(field)
        if use in prohibited and state=='ALLOWED': return 'HOLD_PROHIBITED_USE_MARKED_ALLOWED:'+field
        if use in allowed and state=='PROHIBITED': return 'HOLD_PERMITTED_USE_MARKED_PROHIBITED:'+field
    if x.get('attribution_requirement') not in {'NONE','REQUIRED','CONTRACTUAL'}: return 'HOLD_ATTRIBUTION_STATE_INVALID'
    if x.get('attribution_requirement') in {'REQUIRED','CONTRACTUAL'} and x.get('attribution_satisfied') is not True: return 'HOLD_ATTRIBUTION_REQUIRED'
    # provenance is useful but not legal permission; contradiction must remain visible
    if x.get('provenance_integrity_state') not in {'VERIFIED','PARTIAL','UNAVAILABLE'}: return 'HOLD_PROVENANCE_STATE_INVALID'
    if x.get('content_credential_ref') not in {'NONE','NO_CREDENTIAL'} and x.get('content_credential_valid') is not True: return 'HOLD_CONTENT_CREDENTIAL_INVALID'
    if x.get('rights_conflict_state') not in {'NONE','DISPUTED','CONTRADICTORY','OPEN_REVIEW'}: return 'HOLD_RIGHTS_CONFLICT_STATE_INVALID'
    if x.get('rights_conflict_state')!='NONE':
        if x.get('decision_state') in {'CLEARED','CLEARED_FOR_PUBLICATION','CLEARED_FOR_USE'}: return 'HOLD_RIGHTS_CONFLICT'
        return 'RIGHTS_HOLD_REVIEW_READY'
    if x.get('rights_review_required') not in {'YES','NO'}: return 'HOLD_REVIEW_REQUIRED_STATE_INVALID'
    if x.get('rights_review_required')=='YES' and x.get('legal_review_state') not in {'REQUIRED_OPEN','APPROVED'}: return 'HOLD_LEGAL_REVIEW_STATE_INVALID'
    # AI-output copyrightability cannot be inferred from prompt-only or unknown human contribution
    if x.get('asset_type') in {'AI_OUTPUT','TEXT_MODEL_ASSISTED','IMAGE_MODEL_ASSISTED'} and x.get('human_contribution_state') in {'PROMPT_ONLY','UNKNOWN'} and x.get('decision_state')=='COPYRIGHT_OWNERSHIP_CONFIRMED':
        return 'HOLD_AI_AUTHORSHIP_OVERCLAIM'
    # transfer must recompute, never reuse baseline permission or decision
    if x.get('transfer_variant') not in {'BASELINE','NONE',None}:
        if x.get('transfer_recomputed') is not True: return 'HOLD_TRANSFER_NOT_RECOMPUTED'
        if x.get('baseline_decision_reused') is True: return 'HOLD_TRANSFER_BASELINE_DECISION_REUSE'
        if x.get('baseline_permission_reused') is True: return 'HOLD_TRANSFER_BASELINE_PERMISSION_REUSE'
        if x.get('decision_state') in {'CLEARED_FOR_PUBLICATION','PARTIAL'}: return 'TRANSFER_RIGHTS_REVIEW_READY'
    if x.get('decision_state') in {'CLEARED','CLEARED_FOR_PUBLICATION','CLEARED_FOR_USE'}: return 'RIGHTS_CLEARED_REVIEW_READY'
    if x.get('decision_state')=='PARTIAL': return 'RIGHTS_PARTIAL_REVIEW_READY'
    return 'RIGHTS_HOLD_REVIEW_READY'
