from __future__ import annotations
import copy, json

def _get(d, path):
    cur=d
    for part in path.split('.'):
        if part.isdigit(): cur=cur[int(part)]
        else: cur=cur[part]
    return cur

def apply_probe(data, probe):
    d=copy.deepcopy(data); parts=probe['path'].split('.'); cur=d
    for part in parts[:-1]: cur=cur[int(part)] if part.isdigit() else cur[part]
    last=parts[-1]
    if last.isdigit(): cur[int(last)]=probe['value']
    else: cur[last]=probe['value']
    return d

def evaluate(data, policy):
    a=data['artifact']; s=data['sbom']; p=data['provenance']; att=data['attestation']
    artifact_digest=a['digest']['sha256']
    if a['id'] != policy['expected_artifact_id'] or artifact_digest != policy['expected_artifact_digest']:
        return {'decision':'DENY_ARTIFACT_IDENTITY','failed_boundary':'ARTIFACT_IDENTITY'}
    if s['target']['name'] != a['name'] or s['target']['version'] != a['version'] or s['target']['digest']['sha256'] != artifact_digest:
        return {'decision':'DENY_SBOM_ARTIFACT_MISMATCH','failed_boundary':'SBOM_ARTIFACT_BINDING'}
    present={c['name'] for c in s['components']}
    missing=[x for x in policy['required_components'] if x not in present]
    if missing:
        return {'decision':'DENY_SBOM_COVERAGE','failed_boundary':'SBOM_COVERAGE','missing_components':missing}
    if p.get('predicateType') != policy['expected_predicate_type']:
        return {'decision':'DENY_PROVENANCE_TYPE','failed_boundary':'PROVENANCE_TYPE'}
    subjects=p.get('subject') or []
    if len(subjects)!=1 or subjects[0].get('digest',{}).get('sha256') != artifact_digest:
        return {'decision':'DENY_PROVENANCE_SUBJECT_MISMATCH','failed_boundary':'PROVENANCE_SUBJECT_BINDING'}
    builder=p['predicate']['runDetails']['builder']['id']
    if builder not in policy['approved_builders']:
        return {'decision':'DENY_UNAPPROVED_BUILDER','failed_boundary':'BUILDER_POLICY','builder_id':builder}
    if not att.get('synthetic_verified') or att.get('identity') not in policy['approved_attestors']:
        return {'decision':'DENY_ATTESTATION_IDENTITY','failed_boundary':'ATTESTATION_POLICY'}
    deps=p['predicate']['buildDefinition'].get('resolvedDependencies',[])
    rev=None
    for dep in deps:
        if 'gitCommit' in dep.get('digest',{}): rev=dep['digest']['gitCommit']; break
    if rev != policy['expected_source_revision']:
        return {'decision':'DENY_SOURCE_REVISION','failed_boundary':'SOURCE_REVISION','source_revision':rev}
    return {'decision':'ACCEPT_EVIDENCE_SET','failed_boundary':None,'artifact_digest':artifact_digest,'component_count':len(s['components']),'builder_id':builder,'source_revision':rev,'attestation_identity':att['identity']}
