"""Run: python3 -B -m unittest discover -s public/research/phi-detector -v."""
from fractions import Fraction
import json
import math
from pathlib import Path
import unittest
from model import calculations, exponential_phi, hypothetical_posterior, normal_phi, result


class ScoreTests(unittest.TestCase):
    def test_normal_reference_values(self):
        self.assertAlmostEqual(normal_phi(1, 1, .1), math.log10(2), places=14)
        # Standard normal upper-tail reference at z=2 and z=5.
        self.assertAlmostEqual(normal_phi(1.2, 1, .1), -math.log10(.02275013194817921), places=12)
        self.assertAlmostEqual(normal_phi(1.5, 1, .1), -math.log10(2.866515718791933e-7), places=12)

    def test_exponential_inverse_and_zero(self):
        self.assertEqual(exponential_phi(0, 1), 0)
        for mean in [.01, 1, 1000]:
            for threshold in [1, 3, 8]:
                self.assertAlmostEqual(exponential_phi(mean * math.log(10) * threshold, mean), threshold)

    def test_unit_invariance(self):
        for d in [0, 1, 1.2, 1.5, 2]:
            self.assertAlmostEqual(exponential_phi(d, 1), exponential_phi(d * 1000, 1000))
            self.assertAlmostEqual(normal_phi(d, 1, .1), normal_phi(d * 1000, 1000, 100))

    def test_monotonicity(self):
        for fn in [lambda d: normal_phi(d, 1, .1), lambda d: exponential_phi(d, 1)]:
            values = [fn(d / 10) for d in range(1, 31)]
            self.assertEqual(values, sorted(values))

    def test_invalid_parameters(self):
        for bad in [-1, math.inf, math.nan]:
            with self.assertRaises(ValueError):
                exponential_phi(bad, 1)
            with self.assertRaises(ValueError):
                normal_phi(bad, 1, .1)
        for bad in [0, -1, math.inf, math.nan]:
            with self.assertRaises(ValueError):
                exponential_phi(1, bad)
            with self.assertRaises(ValueError):
                normal_phi(1, 1, bad)
        with self.assertRaises(ValueError):
            normal_phi(100, 1, .1)

    def test_bayes_exact_and_prior_dependence(self):
        a = hypothetical_posterior(Fraction(1, 10000), Fraction(1, 1000))
        b = hypothetical_posterior(Fraction(1, 100), Fraction(1, 1000))
        self.assertEqual(a, Fraction(1000, 10999))
        self.assertEqual(b, Fraction(1000, 1099))
        self.assertLess(a, b)
        for p, tail in [(0, .1), (1, .1), (.1, 0), (.1, 2), (math.nan, .1)]:
            with self.assertRaises(ValueError):
                hypothetical_posterior(p, tail)

    def test_generated_numbers_and_provenance(self):
        generated = json.loads(Path(__file__).with_name("results.json").read_text())
        for key, value in calculations().items():
            self.assertEqual(generated[key], value)
        self.assertEqual(generated["sourceCodeSha256"], result()["sourceCodeSha256"])
        self.assertNotEqual(generated["generatedAt"], generated["sourcesReviewedOn"])


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