import copy

REQUIRED_EVIDENCE_FIELDS = [
    "data_class","processing_purpose","owner","retention_trigger","retention_condition",
    "export_scope","deletion_scope","hold_state","observed_evidence","residual_boundary"
]

def _class_policy(record, policy):
    cls = str(record.get("data_class", ""))
    cp = policy.get("classes", {}).get(cls)
    if not cp:
        return None, "DENY_UNKNOWN_DATA_CLASS"
    if record.get("processing_purpose") != cp.get("purpose") or record.get("owner") != cp.get("owner"):
        return None, "DENY_CLASS_PURPOSE_OWNER_MISMATCH"
    return cp, None

def classify_record(record, policy):
    cp, err = _class_policy(record, policy)
    if err:
        return {"decision": err, "data_class": record.get("data_class")}
    return {"decision": "ALLOW_CLASSIFIED_CONTEXT", "data_class": record["data_class"], "purpose": cp["purpose"], "owner": cp["owner"]}

def retention_decision(record, policy):
    cp, err = _class_policy(record, policy)
    if err:
        return {"decision": err}
    r = cp.get("retention", {})
    if not all(r.get(k) for k in ("trigger","condition","exit_condition")):
        return {"decision": "DENY_RETENTION_RULE_INCOMPLETE"}
    return {"decision":"ALLOW_RETENTION_RULE","trigger":r["trigger"],"condition":r["condition"],"exit_condition":r["exit_condition"],"policy_authority":policy.get("policy_authority","UNSPECIFIED")}

def evaluate_export(request, records, policy):
    rec = next((r for r in records if r.get("record_id") == request.get("record_id")), None)
    if not rec:
        return {"decision":"DENY_UNKNOWN_RECORD"}
    cp, err = _class_policy(rec, policy)
    if err:
        return {"decision":err}
    exp = cp.get("export", {})
    if request.get("requester_role") not in exp.get("authorized_roles", []):
        return {"decision":"DENY_EXPORT_AUTHORITY"}
    requested = request.get("requested_fields", [])
    if not requested or not set(requested).issubset(set(exp.get("allowed_fields", []))):
        return {"decision":"DENY_EXPORT_FIELD_SCOPE"}
    if request.get("processing_purpose") != cp.get("purpose"):
        return {"decision":"DENY_EXPORT_PURPOSE_MISMATCH"}
    return {"decision":"ALLOW_EXPORT_MINIMIZED","exported_fields":list(requested),"data_class":rec["data_class"]}

def evaluate_hold(hold, policy):
    if not hold or not hold.get("active"):
        return {"decision":"NO_ACTIVE_HOLD"}
    if not hold.get("reason") or not hold.get("owner"):
        return {"decision":"DENY_HOLD_METADATA_INCOMPLETE"}
    return {"decision":"HOLD_ACTIVE","authority":policy.get("hold",{}).get("authority","SYNTHETIC_POLICY_HOLD"),"reason":hold["reason"],"owner":hold["owner"]}

def evaluate_deletion(record, disposition, hold, policy):
    cp, err = _class_policy(record, policy)
    if err:
        return {"decision":err}
    h = evaluate_hold(hold, policy)
    if h["decision"] == "HOLD_ACTIVE":
        if cp.get("hold_allowed") is not True:
            return {"decision":"DENY_HOLD_NOT_ALLOWED","data_class":record.get("data_class")}
        return {"decision":"DELETE_SUSPENDED_HOLD","hold_reason":h["reason"],"hold_owner":h["owner"]}
    if h["decision"].startswith("DENY_"):
        return h
    if disposition.get("ui_hidden_only") is True:
        return {"decision":"DENY_UI_INVISIBILITY_NOT_DISPOSITION"}
    if disposition.get("retention_exit_satisfied") is False:
        return {"decision":"DENY_RETENTION_EXIT_NOT_SATISFIED"}
    required = cp.get("deletion_scope", [])
    if "primary" in required and disposition.get("primary_disposed") is not True:
        return {"decision":"DENY_DELETION_EVIDENCE_INCOMPLETE"}
    if "owned_derivative" in required and disposition.get("derived_disposed") is not True:
        return {"decision":"DENY_DELETION_EVIDENCE_INCOMPLETE"}
    if disposition.get("backup_boundary") != policy.get("deletion",{}).get("accepted_backup_boundary"):
        return {"decision":"DENY_BACKUP_BOUNDARY_UNDOCUMENTED"}
    return {"decision":"DELETE_BOUNDED_DISPOSITION_RECORDED","deletion_scope":list(required),"backup_boundary":disposition["backup_boundary"]}

def evidence_row_complete(row):
    if not isinstance(row, dict):
        return False
    if set(row.keys()) != set(REQUIRED_EVIDENCE_FIELDS):
        return False
    return all(k in row and row[k] not in (None,"",[],{}) for k in REQUIRED_EVIDENCE_FIELDS)

def build_evidence_row(record, retention, export_result, deletion_result, hold_state="NONE"):
    return {
      "data_class":record.get("data_class",""),
      "processing_purpose":record.get("processing_purpose",""),
      "owner":record.get("owner",""),
      "retention_trigger":retention.get("trigger",""),
      "retention_condition":retention.get("condition",""),
      "export_scope":",".join(export_result.get("exported_fields",[])) if export_result else "NOT_EXERCISED",
      "deletion_scope":",".join(deletion_result.get("deletion_scope",[])) if deletion_result else "NOT_EXERCISED",
      "hold_state":hold_state,
      "observed_evidence":" / ".join(x for x in [retention.get("decision",""), export_result.get("decision","") if export_result else "", deletion_result.get("decision","") if deletion_result else ""] if x),
      "residual_boundary":"Synthetic/local fixture only; no jurisdiction-specific law, production privacy, or global erasure proof."
    }

def classify_transfer(context):
    if context == "analytics_dataset":
        return {"status":"REBUILD_LIFECYCLE","reason":"owners_and_purposes_changed","copy_baseline_retention":False,"copy_baseline_export_scope":False}
    if context == "support_dataset":
        return {"status":"REBUILD_LIFECYCLE","reason":"re-evaluate_declared_context","copy_baseline_retention":False,"copy_baseline_export_scope":False}
    return {"status":"BOUNDED_DEFER","reason":"unknown_changed_context"}
