"""Learn a small verified procedure. No model, account, broker or server required."""
from pathlib import Path
from tempfile import mkdtemp
from soterias import Procedure, ProcedureMemory

def value(x):
    return {"op": "literal", "value": x}

def input_at(name, pointer=""):
    return {"op": "input", "name": name, "pointer": pointer}

def multiply(left, right):
    return {"op": "multiply", "left": left, "right": right}

environment = dict(policy="1" * 64, tools="2" * 64,
                   credentials="3" * 64, evidence="4" * 64,
                   host_configuration=None)
calculation = multiply(input_at("quantity"), input_at("price"))
graph = {
    "version": 1, "id": "line-total", "environment": environment,
    "inputs": {"quantity": "integer", "price": "integer"},
    "terminal": "result", "nodes": [
        {"id": name, "after": [], "output": "integer",
         "step": {"kind": "pure", "value": calculation}}
        for name in ("calculate", "duplicate")
    ] + [{"id": "result", "after": ["calculate", "duplicate"],
          "output": "integer", "step": {"kind": "verify",
          "condition": value(True),
          "value": {"op": "node", "id": "calculate", "pointer": ""}}}],
}
# The application supplies this separately from the candidate graph.
# Rust builds its context from actual inputs and the completed result.
verifier = {"op": "equal", "left": input_at("context", "/result"),
            "right": multiply(input_at("context", "/inputs/quantity"),
                              input_at("context", "/inputs/price"))}
procedure = Procedure(graph, verifier=verifier)
directory = Path(mkdtemp(prefix="soterias-learn-")) / "memory"
memory = ProcedureMemory.create(directory)
for index in range(5):
    run = procedure.start({"quantity": str(index + 1), "price": "1200"},
                          environment=environment)
    try:
        assert run.advance_pure(environment=environment) == "verified"
        memory.observe(run, phase="training" if index < 3 else
                       "shadow" if index == 3 else "canary")
    finally:
        run.close()
qualified = ProcedureMemory.open(directory).qualify(procedure, environment=environment)
run = qualified.start({"quantity": "6", "price": "1200"}, environment=environment)
try:
    assert run.advance_pure(environment=environment) == "verified"
    assert run.result == "7200"
    assert run.report["independently_verified"] is True
    assert sum(node["reused_pure"] for node in run.report["trace"]) == 1
    print(run.result)
    print(run.report)
    print("Memory:", directory)
finally:
    run.close()
