from copy import deepcopy

REQUIRED_RECORD_FIELDS = [
    "fixture_id","authorized_uploader","declared_type","observed_type_evidence",
    "size_policy","scan_cdr_status","quarantine_location","generated_storage_name",
    "promotion_decision","public_representation","residual_risk"
]

def evidence_row_complete(row):
    return all(k in row and row[k] not in (None, "", []) for k in REQUIRED_RECORD_FIELDS)

def evaluate_upload(item, policy):
    r={"fixture_id":item.get("fixture_id"),"initial_state":"QUARANTINED","state":"QUARANTINED","decision":None,"public_representation":None}
    allowed_roles=policy.get("authorized_roles",[])
    if item.get("uploader_role") not in allowed_roles:
        r["decision"]="DENY_UNAUTHORIZED_UPLOADER"; return r
    ext=item.get("extension","").lower()
    if ext not in policy.get("allowed_extensions",[]):
        r["decision"]="DENY_EXTENSION_NOT_ALLOWED"; return r
    size=int(item.get("size_bytes",0))
    if size<=0:
        r["decision"]="DENY_INVALID_SIZE"; return r
    if size>int(policy.get("max_size_bytes",0)):
        r["decision"]="DENY_SIZE_LIMIT"; return r
    declared=item.get("declared_type")
    observed=item.get("observed_signature_type")
    mapping=policy.get("extension_type_map",{})
    expected=mapping.get(ext)
    if not expected or declared!=expected or observed!=expected:
        r["decision"]="DENY_TYPE_EVIDENCE_MISMATCH"; return r
    generated=item.get("generated_storage_name")
    if not generated:
        r["decision"]="DENY_STORAGE_IDENTITY_MISSING"; return r
    if generated==item.get("client_filename"):
        r["decision"]="DENY_CLIENT_FILENAME_AS_STORAGE_IDENTITY"; return r
    quarantine=item.get("quarantine_location","")
    if not quarantine:
        r["decision"]="DENY_QUARANTINE_LOCATION_MISSING"; return r
    if quarantine.startswith("public://"):
        r["decision"]="DENY_PUBLIC_RAW_STORAGE"; return r
    if not quarantine.startswith("quarantine://"):
        r["decision"]="DENY_QUARANTINE_LOCATION_INVALID"; return r
    required=bool(policy.get("scan_required_by_extension",{}).get(ext,False))
    scan=item.get("scan_cdr_status")
    if required and scan!="PASS_SYNTHETIC":
        r["decision"]="DENY_REQUIRED_GATE_MISSING"; return r
    if scan not in {"PASS_SYNTHETIC","NOT_REQUIRED_FOR_FIXTURE"}:
        r["decision"]="DENY_SCAN_STATUS"; return r
    r["state"]="PROMOTED"; r["decision"]="PROMOTE_APPROVED_REPRESENTATION"; r["public_representation"]="approved://"+item["generated_storage_name"]
    return r

def build_record(item, result):
    return {
      "fixture_id":item["fixture_id"],
      "authorized_uploader":item["uploader_role"],
      "declared_type":item["declared_type"],
      "observed_type_evidence":item["observed_signature_type"],
      "size_policy":f"{item['size_bytes']} bytes",
      "scan_cdr_status":item["scan_cdr_status"],
      "quarantine_location":item["quarantine_location"],
      "generated_storage_name":item.get("generated_storage_name"),
      "promotion_decision":result["decision"],
      "public_representation":result.get("public_representation") or "NONE",
      "residual_risk":"Synthetic fixture proves only declared gates; no production safety claim."
    }

def classify_transfer(kind):
    if kind=="avatar":
      return {"status":"REBUILD_POLICY","allowed_extensions":[".png",".jpg"],"scan_required":False,"downstream_consumer":"image_renderer"}
    if kind=="contract_document":
      return {"status":"REBUILD_POLICY","allowed_extensions":[".pdf"],"scan_required":True,"downstream_consumer":"document_viewer"}
    return {"status":"UNKNOWN_CONTEXT","allowed_extensions":[],"scan_required":None,"downstream_consumer":None}
