"""Synthetic score-contract fixture. Standard library; no model or database calls."""
import math
import sys
import unittest


def unit(v):
    if not v or any(not math.isfinite(x) for x in v):
        raise ValueError("Expected a nonempty finite vector")
    n = math.hypot(*v)
    if not math.isfinite(n) or n == 0:
        raise ValueError("Expected a finite nonzero norm")
    return tuple(x / n for x in v)


def dot(a, b):
    if len(a) != len(b):
        raise ValueError("Dimension mismatch")
    return sum(x * y for x, y in zip(a, b))


def scores(a, b):
    a, b = unit(a), unit(b)
    s = max(-1.0, min(1.0, dot(a, b)))
    squared = sum((x - y) ** 2 for x, y in zip(a, b))
    return {"cosine_similarity": s, "cosine_distance": 1 - s,
            "squared_l2": squared, "l2": math.sqrt(squared)}


def accepts(score, metric, threshold=0.8):
    """threshold is a cosine-similarity cutoff; L2 inputs MUST be unit-vector distances."""
    if not math.isfinite(score) or not math.isfinite(threshold) or not -1 <= threshold <= 1:
        raise ValueError("Invalid score or cosine threshold")
    if metric == "cosine_similarity":
        return score >= threshold
    if metric == "cosine_distance":
        return score <= 1 - threshold
    if metric == "squared_l2":
        return score <= 2 - 2 * threshold
    if metric == "l2":
        return score <= math.sqrt(2 - 2 * threshold)
    raise ValueError("Unknown metric")


QUERY = (1.0, 0.0)
DOCS = {"A": (0.9, math.sqrt(0.19)),
        "B": (0.7, math.sqrt(0.51)),
        "C": (0.3, math.sqrt(0.91))}


def demo():
    rows = {name: scores(QUERY, v) for name, v in DOCS.items()}
    print("document  cosine  cosine_distance  squared_l2  l2")
    for name, s in rows.items():
        print(f"{name:8}  {s['cosine_similarity']:.3f}   {s['cosine_distance']:.3f}            {s['squared_l2']:.3f}       {s['l2']:.3f}")
    print("cosine >= 0.8:", ",".join(n for n, s in rows.items() if accepts(s['cosine_similarity'], 'cosine_similarity')))
    print("wrong squared_l2 <= 0.8:", ",".join(n for n, s in rows.items() if s['squared_l2'] <= 0.8))
    print("correct squared_l2 <= 0.4:", ",".join(n for n, s in rows.items() if accepts(s['squared_l2'], 'squared_l2')))
    print("wrong cosine_distance >= 0.8:", ",".join(n for n, s in rows.items() if s['cosine_distance'] >= 0.8) or "none")


class ContractTests(unittest.TestCase):
    def test_normalise(self):
        self.assertEqual(unit((3, 4)), (0.6, 0.8))

    def test_zero_rejected(self):
        with self.assertRaises(ValueError): unit((0, 0))

    def test_nonfinite_rejected(self):
        for v in [(math.nan, 1), (math.inf, 0), ()]:
            with self.subTest(v=v), self.assertRaises(ValueError): unit(v)

    def test_dimensions(self):
        with self.assertRaises(ValueError): scores((1, 0), (1,))

    def test_identity(self):
        for v in DOCS.values():
            s = scores(QUERY, v)
            self.assertAlmostEqual(s['squared_l2'], 2 - 2 * s['cosine_similarity'])

    def test_rank_agreement(self):
        rows = {n: scores(QUERY, v) for n, v in DOCS.items()}
        for metric in ('cosine_similarity', 'cosine_distance', 'squared_l2', 'l2'):
            self.assertEqual(sorted(rows, key=lambda n: rows[n][metric], reverse=metric == 'cosine_similarity'), ['A', 'B', 'C'])

    def test_threshold_equivalence(self):
        for n, v in DOCS.items():
            for metric, score in scores(QUERY, v).items():
                with self.subTest(n=n, metric=metric):
                    self.assertEqual(accepts(score, metric), n == 'A')

    def test_wrong_threshold_admits_b(self):
        s = scores(QUERY, DOCS['B'])
        self.assertLessEqual(s['squared_l2'], 0.8)
        self.assertFalse(accepts(s['squared_l2'], 'squared_l2'))

    def test_wrong_direction_rejects_a(self):
        self.assertFalse(scores(QUERY, DOCS['A'])['cosine_distance'] >= 0.8)

    def test_endpoints(self):
        for v in ((1, 0), (-1, 0), (0, 1)):
            s = scores(QUERY, v)
            self.assertAlmostEqual(s['squared_l2'], 2 - 2 * s['cosine_similarity'])
        self.assertTrue(accepts(0.8, 'cosine_similarity'))
        self.assertTrue(accepts(2 - 2 * 0.8, 'squared_l2'))

    def test_unnormalised_inner_product_changes_rank(self):
        a, b = (0.9, math.sqrt(0.19)), (7.0, math.sqrt(51.0))
        self.assertGreater(dot(QUERY, b), dot(QUERY, a))
        self.assertLess(scores(QUERY, b)['cosine_similarity'], scores(QUERY, a)['cosine_similarity'])

    def test_bad_contract_rejected(self):
        for score, metric, threshold in [(math.nan, 'l2', 0.8), (0.2, 'unknown', 0.8), (0.2, 'l2', 2)]:
            with self.subTest(metric=metric), self.assertRaises(ValueError): accepts(score, metric, threshold)


if __name__ == '__main__':
    if '--test' in sys.argv:
        unittest.main(argv=[sys.argv[0]], verbosity=2)
    else:
        demo()
