#!/usr/bin/env python3
"""Offline IP allowlist policy exercise. Python 3.9+, standard library only.

All addresses are documentation examples, not observed peers or usable endpoints.
No sockets, requests, credentials, provider configuration or firewall changes.
The model uses source-address rules at two explicitly named enforcement points.
It does not model TLS, target login, protocol support or trusted CDN header rules.
eligible_for_trial means only that these synthetic address checks matched.
"""
import copy
import ipaddress
import json
from collections import Counter


def member(address, networks):
    if address is None:
        return None
    ip = ipaddress.ip_address(address)
    parsed = [ipaddress.ip_network(n, strict=True) for n in networks]
    return any(ip.version == n.version and ip in n for n in parsed)


def evaluate(case):
    route = case["route"]
    if route not in ("proxy", "direct"):
        raise ValueError("Declare proxy or direct route")
    ingress = (member(case["client_at_provider"], case["provider_allowlist"])
               if route == "proxy" else "not_applicable")
    target = member(case["source_at_target_gate"], case["target_allowlist"])
    if ingress is False:
        decision = "blocked_ingress"
        target = "not_reached"
    elif ingress is None:
        decision = "pending_evidence"
        target = "not_evaluated"
    elif target is None:
        decision = "pending_evidence"
    elif not target:
        decision = "blocked_target"
    else:
        decision = "eligible_for_trial"
    return {"case": case["name"], "provider_gate": ingress,
            "target_gate": target, "decision": decision}


def cases():
    base = {"route": "proxy", "client_at_provider": "192.0.2.10",
            "provider_endpoint": "203.0.113.40",
            "provider_allowlist": ["192.0.2.10/32"],
            "source_at_target_gate": "198.51.100.20",
            "target_allowlist": ["198.51.100.20/32"],
            "untrusted_forwarded_for": None}
    specs = [
        ("fixed_egress", {}, "eligible_for_trial"),
        ("same_endpoint_changed_exit", {"source_at_target_gate": "198.51.100.21"}, "blocked_target"),
        ("worker_network_changed", {"client_at_provider": "192.0.2.11"}, "blocked_ingress"),
        ("unapproved_backup_exit", {"source_at_target_gate": "198.51.100.22"}, "blocked_target"),
        ("approved_direct_route", {"route": "direct", "source_at_target_gate": "192.0.2.10", "target_allowlist": ["192.0.2.10/32"]}, "eligible_for_trial"),
        ("header_does_not_change_peer", {"source_at_target_gate": "198.51.100.21", "untrusted_forwarded_for": "198.51.100.20"}, "blocked_target"),
        ("ipv6_not_in_ipv4_rule", {"source_at_target_gate": "2001:db8:2::20"}, "blocked_target"),
        ("explicit_ipv6_rule", {"source_at_target_gate": "2001:db8:2::20", "target_allowlist": ["2001:db8:2::20/128"]}, "eligible_for_trial"),
        ("target_source_unknown", {"source_at_target_gate": None}, "pending_evidence"),
        ("provider_source_unknown", {"client_at_provider": None}, "pending_evidence"),
        ("approved_backup_set", {"source_at_target_gate": "198.51.100.22", "target_allowlist": ["198.51.100.20/32", "198.51.100.22/32"]}, "eligible_for_trial"),
    ]
    result = []
    for name, changes, expected in specs:
        case = copy.deepcopy(base)
        case.update(changes, name=name, expected=expected)
        result.append(case)
    return result


def run():
    examples = cases()
    results = [evaluate(c) for c in examples]
    for c, r in zip(examples, results):
        if r["decision"] != c["expected"]:
            raise AssertionError((c["name"], r, c["expected"]))
    # Counterexamples: the endpoint and an untrusted header cannot prove egress.
    changed_endpoint = copy.deepcopy(examples[1])
    changed_endpoint["provider_endpoint"] = "203.0.113.99"
    if evaluate(changed_endpoint)["decision"] != "blocked_target":
        raise AssertionError("Endpoint name/address substituted for target peer")
    changed_header = copy.deepcopy(examples[0])
    changed_header["untrusted_forwarded_for"] = "198.51.100.99"
    if evaluate(changed_header)["decision"] != "eligible_for_trial":
        raise AssertionError("Untrusted header changed source-address policy")
    missing_backup_approval = copy.deepcopy(examples[-1])
    missing_backup_approval["target_allowlist"] = ["198.51.100.20/32"]
    if evaluate(missing_backup_approval)["decision"] != "blocked_target":
        raise AssertionError("Unapproved backup accepted")
    return {"mode": "offline_synthetic_policy_model", "network_requests": 0,
            "cases": results, "counts": dict(sorted(Counter(r["decision"] for r in results).items())),
            "counterexamples_passed": 3,
            "limits": "No actual IP, socket, TLS, vendor policy or IPHTML service was tested. Matching address rules is not application acceptance."}


if __name__ == "__main__":
    print(json.dumps(run(), indent=2))
