"""Independent exact arithmetic checks and rejection cases; no model weights involved."""
import hashlib,json,random,unittest
from fractions import Fraction
from bounded_arithmetic import calculate,result,ArithmeticRefusal

class ArithmeticTests(unittest.TestCase):
    def value(self,x):
        r=calculate(x);return Fraction(int(r['numerator']),int(r['denominator']))
    def test_decimal_exact(self):self.assertEqual(self.value('0.1+0.2'),Fraction(3,10))
    def test_assignment(self):self.assertEqual(self.value('cost = 35 * 12\nanswer = cost / 7'),60)
    def test_negative_floor(self):self.assertEqual(self.value('floor(-7/3)'),-3)
    def test_ceil(self):self.assertEqual(self.value('ceil(7/3)'),3)
    def test_modulo(self):self.assertEqual(self.value('7 % 3'),1)
    def test_power(self):self.assertEqual(self.value('3 ** -2'),Fraction(1,9))
    def test_bankers_round(self):self.assertEqual(self.value('round(2.5)'),2)
    def test_min_max(self):self.assertEqual(self.value('max(3, 8/2, min(9,7))'),7)
    def test_code_fence(self):self.assertEqual(self.value('```python\nanswer = 2+3\n```'),5)
    def test_digest(self):self.assertEqual(calculate(' 2+3 ')['input_sha256'],hashlib.sha256(b' 2+3 ').hexdigest())
    def test_trace(self):self.assertEqual(len(calculate('a=1\nb=2\nanswer=a+b')['trace']),3)
    def test_no_semantic_claim(self):self.assertFalse(calculate('3')['word_problem_translation_verified'])
    def test_no_authority(self):self.assertFalse(calculate('3')['external_action_authority'])
    def test_refusal_drops_old_result(self):
        ok=result('4');bad=result('open("x")');self.assertEqual(ok['status'],'COMPUTED');self.assertIsNone(bad['result'])
    def test_hostile_and_out_of_scope(self):
        bad=['import os','__import__("os")','open("x")','().__class__','[x for x in range(5)]','lambda: 1','exec("1")','True+1','"2"+"3"',
             'a=[1]','while True: pass','answer=1\nprint(answer)','a=b=2','a=1\na=2\nanswer=a','answer=1\nb=2','2**99','2**0.5',
             '1/0','0**-1','1e999','0xff','1_000','round(2,99)','pow(2,3)','answer = unknown','a=2','abs(1,2)','min()','round(1,2,3)',
             'answer=1\nanswer=2','answer = [1][0]','answer = 1 if True else 2','answer = sum([1,2])', 'answer = 2 << 6',
             'answer = 1\n__x=2','```python\nanswer=2\n```\nprose']
        for source in bad:
            with self.subTest(source=source),self.assertRaises(ArithmeticRefusal):calculate(source)
    def test_bounds(self):
        for source in ['1'*4097,'a = '+ '1+'*300+'1','('*300+'1'+')'*300,'10**6 * 10**6 * 10**6 * 10**6 * 10**6 * 10**6 * 10**6 * 10**6 * 10**6']:
            with self.subTest(source=source[:25]),self.assertRaises(ArithmeticRefusal):calculate(source)
    def test_exponent_guard_before_fraction(self):
        from unittest.mock import patch
        for value in ['1e-999999999','1e999999999','1e-49','1e+49']:
            with self.subTest(value=value), patch('bounded_arithmetic.Fraction',side_effect=AssertionError('allocation reached')):
                with self.assertRaises(ArithmeticRefusal):calculate(value)
    def test_scientific_fraction(self):self.assertEqual(self.value('1e-6'),Fraction(1,1000000))
    def test_prose_is_refused(self):self.assertEqual(result('There are five apples.')['status'],'REFUSED')
    def test_seeded_parity(self):
        rng=random.Random(20261110)
        for _ in range(160):
            a,b,c=[rng.randint(-500,500) for _ in range(3)];c=c or 1
            expr=f'({a}+{b})*3/{c}'
            with self.subTest(expr=expr):self.assertEqual(self.value(expr),Fraction((a+b)*3,c))

if __name__=='__main__':unittest.main()
