"""Synthetic admission fixture. No network, model or real external effects.
Run: python3 agent-boolean-limit.py [--test]
Contract: max_calls is a required JSON integer token, from 0 through 3.
"""
import json
import sys
import unittest

SERVER_CAP = 3
ITEMS = ("a", "b", "c", "d", "e")


def unsafe(raw, effects):
    request = json.loads(raw)
    limit = request["max_calls"]
    if not isinstance(limit, int) or not 0 <= limit <= SERVER_CAP:
        raise ValueError("bad limit")
    # Deliberate bug: zero means unlimited, including decoded false.
    selected = ITEMS[:limit] if limit else ITEMS
    effects.extend(selected)
    return len(selected)


def unique_object(pairs):
    result = {}
    for key, value in pairs:
        if key in result:
            raise ValueError("duplicate member")
        result[key] = value
    return result


def reject_constant(value):
    raise ValueError("non-JSON constant")


def guarded(raw, effects):
    request = json.loads(raw, object_pairs_hook=unique_object,
                         parse_constant=reject_constant)
    if type(request) is not dict or set(request) != {"max_calls"}:
        raise ValueError("expected max_calls only")
    limit = request["max_calls"]
    # Deliberately stricter than JSON Schema's mathematical integer type.
    if type(limit) is not int or not 0 <= limit <= SERVER_CAP:
        raise ValueError("max_calls must be an integer token from 0 to 3")
    selected = ITEMS[:limit]  # zero admits no work
    effects.extend(selected)
    return len(selected)


class AdmissionTests(unittest.TestCase):
    def reject(self, raw):
        effects = []
        with self.assertRaises(ValueError):
            guarded(raw, effects)
        self.assertEqual(effects, [])

    def test_unsafe_false_runs_all(self):
        effects = []
        self.assertEqual(unsafe('{"max_calls":false}', effects), 5)
        self.assertEqual(effects, list(ITEMS))

    def test_unsafe_zero_runs_all(self):
        self.assertEqual(unsafe('{"max_calls":0}', []), 5)

    def test_false_rejected(self):
        self.reject('{"max_calls":false}')

    def test_true_rejected(self):
        self.reject('{"max_calls":true}')

    def test_zero_runs_none(self):
        effects = []
        self.assertEqual(guarded('{"max_calls":0}', effects), 0)
        self.assertEqual(effects, [])

    def test_valid_counts(self):
        for limit in (1, 2, 3):
            with self.subTest(limit=limit):
                effects = []
                self.assertEqual(guarded(json.dumps({"max_calls":limit}), effects), limit)
                self.assertEqual(effects, list(ITEMS[:limit]))

    def test_out_of_range(self):
        for value in (-1, 4, 1000000):
            with self.subTest(value=value):
                self.reject(json.dumps({"max_calls":value}))

    def test_coercions_rejected(self):
        for token in ('"2"', '2.0', '2e0', 'null', '[]', '{}'):
            with self.subTest(token=token):
                self.reject('{"max_calls":' + token + '}')

    def test_missing_extra_and_wrong_root(self):
        for raw in ('{}', '[]', 'null', '{"max_calls":1,"unlimited":true}'):
            with self.subTest(raw=raw):
                self.reject(raw)

    def test_duplicate_rejected(self):
        self.reject('{"max_calls":1,"max_calls":0}')

    def test_nonfinite_rejected(self):
        for token in ('NaN', 'Infinity', '-Infinity'):
            with self.subTest(token=token):
                self.reject('{"max_calls":' + token + '}')

    def test_malformed_rejected(self):
        self.reject('{"max_calls":')


if __name__ == "__main__":
    if "--test" in sys.argv:
        unittest.main(argv=[sys.argv[0]], verbosity=2)
    else:
        effects = []
        unsafe('{"max_calls":false}', effects)
        print("unsafe_false=" + ",".join(effects))
        effects = []
        try:
            guarded('{"max_calls":false}', effects)
        except ValueError:
            print("guarded_false=rejected")
        print("guarded_false_effects=" + str(len(effects)))
        print("guarded_zero=" + str(guarded('{"max_calls":0}', [])))
        print("guarded_two=" + str(guarded('{"max_calls":2}', [])))
