REQUIRED_EVIDENCE_FIELDS = ["connection","tls_version_source","server_identity","client_identity","trust_anchor","authenticated","authorized_action","failure_boundary","observed_evidence","residual_risk"]

def _ctx(name, policy):
    return policy.get("contexts",{}).get(name)

def _nonblank(value):
    return isinstance(value,str) and bool(value.strip())

def _trust_domain_from_spiffe(identity):
    if not isinstance(identity,str) or not identity.startswith("spiffe://"):
        return None
    rest=identity[len("spiffe://"):]
    return rest.split('/',1)[0] if rest else None

def evaluate_server_identity(context_name, policy):
    ctx=_ctx(context_name,policy)
    if not ctx: return {"decision":"DENY_UNKNOWN_CONTEXT","failure_boundary":"SERVICE_IDENTITY"}
    if policy.get("current_tls_spec")!="RFC9846": return {"decision":"DENY_STALE_TLS_SPEC","failure_boundary":"TLS_PROTOCOL_SOURCE"}
    if ctx.get("server_identity_status")!="valid": return {"decision":"DENY_SERVER_IDENTITY_EXPIRED","failure_boundary":"SERVICE_IDENTITY"}
    if not _nonblank(ctx.get("trust_anchor")): return {"decision":"DENY_SERVER_TRUST_ANCHOR","failure_boundary":"SERVICE_IDENTITY"}
    if ctx.get("presented_server_identity")!=ctx.get("expected_server_identity"):
        return {"decision":"DENY_SERVER_IDENTITY_MISMATCH","failure_boundary":"SERVICE_IDENTITY"}
    return {"decision":"SERVER_IDENTITY_VERIFIED","failure_boundary":"NONE","server_identity":ctx["expected_server_identity"],"trust_anchor":ctx["trust_anchor"]}

def evaluate_client_identity(context_name, client_identity, client_trust_domain, policy, identity_status=None):
    ctx=_ctx(context_name,policy)
    if not ctx: return {"decision":"DENY_UNKNOWN_CONTEXT","failure_boundary":"CLIENT_AUTHENTICATION"}
    server=evaluate_server_identity(context_name,policy)
    if server["decision"]!="SERVER_IDENTITY_VERIFIED": return server
    configured_status=ctx.get("client_identity_status")
    observed_status=identity_status if identity_status is not None else configured_status
    if configured_status!="valid" or observed_status!="valid": return {"decision":"DENY_CLIENT_IDENTITY_EXPIRED","failure_boundary":"CLIENT_AUTHENTICATION"}
    parsed=_trust_domain_from_spiffe(client_identity)
    if parsed is None or parsed!=client_trust_domain: return {"decision":"DENY_CLIENT_IDENTITY_FORMAT_OR_DOMAIN","failure_boundary":"CLIENT_AUTHENTICATION"}
    if client_trust_domain not in ctx.get("accepted_client_trust_domains",[]): return {"decision":"DENY_CLIENT_TRUST_DOMAIN","failure_boundary":"CLIENT_AUTHENTICATION"}
    return {"decision":"CLIENT_AUTHENTICATED","failure_boundary":"NONE","client_identity":client_identity,"trust_domain":client_trust_domain}

def authorize_action(context_name, client_identity, client_trust_domain, action, policy, identity_status=None):
    auth=evaluate_client_identity(context_name,client_identity,client_trust_domain,policy,identity_status)
    if auth["decision"]!="CLIENT_AUTHENTICATED": return auth
    ctx=_ctx(context_name,policy)
    allowed=ctx.get("allowed_actions",{}).get(client_identity,[])
    if "*" in allowed or "*"==action: return {"decision":"DENY_WILDCARD_AUTHORIZATION","failure_boundary":"AUTHORIZATION","authenticated":True}
    if action not in allowed: return {"decision":"DENY_ACTION_UNAUTHORIZED","failure_boundary":"AUTHORIZATION","authenticated":True}
    return {"decision":"ALLOW_ACTION","failure_boundary":"NONE","authenticated":True,"action":action}

def evaluate_request(connection, policy):
    ctx_name=connection.get("connection")
    if connection.get("tls_version_source")!="RFC9846": return {"decision":"DENY_STALE_TLS_SPEC","failure_boundary":"TLS_PROTOCOL_SOURCE","authenticated":False}
    return authorize_action(ctx_name,connection.get("client_identity"),connection.get("client_trust_domain"),connection.get("action"),policy,connection.get("client_identity_status"))

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

def build_evidence_row(connection, policy):
    ctx_name=connection.get("connection"); ctx=_ctx(ctx_name,policy)
    if not ctx: return {}
    s=evaluate_server_identity(ctx_name,policy); a=evaluate_client_identity(ctx_name,connection.get("client_identity"),connection.get("client_trust_domain"),policy,connection.get("client_identity_status")); z=evaluate_request(connection,policy)
    authenticated=a.get("decision")=="CLIENT_AUTHENTICATED"
    action_label=connection.get("action","") if z.get("decision")=="ALLOW_ACTION" else "DENIED:"+connection.get("action","")
    failure=z.get("failure_boundary","NONE")
    evidence=" / ".join([s.get("decision",""),a.get("decision",""),z.get("decision","")])
    return {"connection":ctx_name,"tls_version_source":connection.get("tls_version_source","") ,"server_identity":ctx.get("presented_server_identity",""),"client_identity":connection.get("client_identity","") ,"trust_anchor":ctx.get("trust_anchor",""),"authenticated":authenticated,"authorized_action":action_label,"failure_boundary":failure,"observed_evidence":evidence,"residual_risk":"Synthetic/local metadata only; does not prove production endpoint, issuer, workload integrity, revocation state, or site authorization policy."}

def classify_transfer(context_name):
    if context_name=="admin_endpoint": return {"status":"REBUILD_IDENTITY_AND_AUTHORIZATION","copy_primary_identity_policy":False,"reason":"different_server_client_and_action_scope"}
    if context_name=="internal_api": return {"status":"REBUILD_IDENTITY_AND_AUTHORIZATION","copy_primary_identity_policy":False,"reason":"independent_primary_context"}
    return {"status":"BOUNDED_DEFER","reason":"unknown_changed_context"}
