"""Offline decision-cache model. No vendor SDK, network, concurrency or latency test."""
import itertools
import json
from pathlib import Path


def evaluate(provider, environment, revision, flag, context):
    """A deliberately bounded, deterministic policy with all dependencies explicit."""
    allowed_tenant = {"blue": "alpha", "green": "bravo"}[provider]
    return (environment == "production" and revision == "r1"
            and flag == "export-v2" and context.get("tenant") == allowed_tenant
            and context.get("role") == "admin" and context.get("region") == "eu"
            and context.get("beta") is True)


def key(strategy, provider, environment, revision, flag, context):
    if strategy == "user_flag":
        return (context.get("user"), flag)
    if strategy == "tenant_user_flag":
        return (context.get("tenant"), context.get("user"), flag)
    encoded = json.dumps(context, sort_keys=True, separators=(",", ":"), allow_nan=False)
    if strategy == "context_flag":
        return (flag, encoded)
    if strategy == "snapshot_context_flag":
        return (provider, environment, revision, flag, encoded)
    raise ValueError(strategy)


class Cache:
    def __init__(self, strategy):
        self.strategy, self.values = strategy, {}
        self.hits = self.misses = 0

    def decide(self, *inputs):
        identity = key(self.strategy, *inputs)
        if identity in self.values:
            self.hits += 1
            return self.values[identity]
        self.misses += 1
        value = evaluate(*inputs)
        self.values[identity] = value
        return value


def grid():
    # Positive alpha / blue / production / r1 / export-v2 arrives first.
    for provider, env, rev, flag, tenant, user, role, region, beta in itertools.product(
            ["blue", "green"], ["production", "staging"], ["r1", "r2"],
            ["export-v2", "export-v3"], ["alpha", "bravo"], ["u1", "u2"],
            ["admin", "viewer"], ["eu", "us"], [True, False]):
        yield provider, env, rev, flag, dict(tenant=tenant, user=user, role=role, region=region, beta=beta)


def run():
    inputs = list(grid())
    assert len(inputs) == 512
    strategies = ["user_flag", "tenant_user_flag", "context_flag", "snapshot_context_flag"]
    comparisons = []
    for strategy in strategies:
        cache = Cache(strategy)
        waves = []
        for wave in [inputs, list(reversed(inputs))]:
            mismatches = false_enabled = false_disabled = 0
            first = None
            for item in wave:
                expected, actual = evaluate(*item), cache.decide(*item)
                if expected != actual:
                    mismatches += 1
                    false_enabled += actual is True
                    false_disabled += actual is False
                    if first is None:
                        first = dict(provider=item[0], environment=item[1], revision=item[2],
                                     flag=item[3], context=item[4], expected=expected, actual=actual)
            waves.append(dict(evaluations=len(wave), mismatches=mismatches,
                              false_enabled=false_enabled, false_disabled=false_disabled,
                              first_mismatch=first))
        comparisons.append(dict(strategy=strategy, waves=waves, entries=len(cache.values),
                                hits=cache.hits, misses=cache.misses))
    complete = comparisons[-1]
    assert complete["entries"] == 512 and complete["hits"] == complete["misses"] == 512
    assert all(w["mismatches"] == 0 for w in complete["waves"])
    assert all(c["waves"][0]["mismatches"] > 0 for c in comparisons[:-1])

    base = ("blue", "production", "r1", "export-v2",
            dict(tenant="alpha", user="u1", role="admin", region="eu", beta=True))
    cases = [("other tenant", {"tenant": "bravo"}, 0, None),
             ("changed role", {"role": "viewer"}, 0, None),
             ("changed region", {"region": "us"}, 0, None),
             ("changed beta", {"beta": False}, 0, None),
             ("typed string beta", {"beta": "true"}, 0, None),
             ("new snapshot", {}, 2, "r2"),
             ("other environment", {}, 1, "staging"),
             ("other provider", {}, 0, "green")]
    transitions = []
    for label, changed, index, replacement in cases:
        item = list(base)
        item[4] = {**base[4], **changed}
        if replacement is not None:
            item[index] = replacement
        cache = Cache("snapshot_context_flag")
        assert cache.decide(*base) is True
        assert evaluate(*item) is False and cache.decide(*item) is False
        transitions.append(dict(case=label, before=True, expected=False, actual=False))

    # A complete identity still cannot notice a snapshot the process has not received.
    local = Cache("snapshot_context_flag")
    assert local.decide(*base) is True
    authoritative = (*base[:2], "r2", *base[3:])
    assert evaluate(*authoritative) is False and local.decide(*base) is True
    result = dict(scope="Constructed sequential Python policy model; no provider SDK or network",
                  unique_inputs=512, waves=2, evaluations_per_strategy=1024,
                  expected_true_per_wave=sum(evaluate(*row) for row in inputs),
                  comparisons=comparisons, transitions=transitions,
                  unseen_snapshot=dict(local_revision="r1", authoritative_revision="r2",
                                       local_value=True, authoritative_value=False,
                                       conclusion="Correct keying does not guarantee configuration freshness"))
    Path("results.json").write_text(json.dumps(result, indent=2) + "\n")
    print(json.dumps({row["strategy"]: [w["mismatches"] for w in row["waves"]]
                      for row in comparisons}))


if __name__ == "__main__":
    run()
