import math
import unittest
from ligninqc.analysis import InputError, compare_table, ranks, summarize


class AnalysisTests(unittest.TestCase):
    def rows(self, xs, ys):
        return [dict(compound_id=str(i), x=str(x), y=str(y)) for i, (x, y) in enumerate(zip(xs, ys))]

    def test_exact_line_and_real_holdout_predictions(self):
        result = summarize(self.rows([1, 2, 3, 4], [9, 7, 5, 3]), "x", "y")
        self.assertAlmostEqual(result["pearson_r"], -1)
        self.assertEqual(result["ranking"]["concordant"], 6)
        self.assertAlmostEqual(result["leave_one_out"]["linear"]["mae"], 0)
        self.assertAlmostEqual(result["leave_one_out"]["training_mean_baseline"]["mae"], 8 / 3)

    def test_rank_ties_and_opposite_direction(self):
        self.assertEqual(ranks([4, 1, 1, 8]), [3, 1.5, 1.5, 4])
        result = summarize(self.rows([1, 1, 2, 3], [1, 1, 2, 3]), "x", "y")
        self.assertEqual(result["ranking"]["tied"], 1)
        self.assertEqual(result["ranking"]["discordant"], 5)

    def test_missing_is_excluded_and_zero_is_preserved(self):
        result = summarize(self.rows([1, 2, 3, 4], [0, 1, 2, ""]), "x", "y")
        self.assertEqual(result["n"], 3)
        self.assertEqual(result["points"][0]["y"], 0)
        self.assertEqual(result["missing"], [{"id": "3", "missing": ["y"]}])

    def test_refuses_uninformative_or_nonfinite_data(self):
        for xs, ys in [([1, 1, 1], [2, 3, 4]), ([1, 2, 3], [2, 2, 2]),
                       ([1, 2, 3], [2, 3, "NaN"]), ([1, 2, 3], [2, 3, "inf"]),
                       ([1, 2, 3], [2, 3, "?"])]:
            with self.subTest(xs=xs, ys=ys), self.assertRaises(InputError):
                summarize(self.rows(xs, ys), "x", "y")

    def test_duplicate_identity_is_not_pseudoreplication(self):
        rows = self.rows([1, 2, 3], [3, 2, 1])
        rows[2]["compound_id"] = rows[1]["compound_id"]
        with self.assertRaises(InputError):
            summarize(rows, "x", "y")

    def test_unfit_fold_is_visible_and_baselines_use_identical_folds(self):
        result = summarize(self.rows([1, 1, 2], [1, 2, 3]), "x", "y")
        folds = result["leave_one_out"]
        self.assertEqual(folds["total_folds"], 3)
        self.assertEqual(folds["evaluated_folds"], 2)
        self.assertIsNone(folds["folds"][2]["linear_prediction"])
        self.assertAlmostEqual(folds["training_mean_baseline"]["mae"], .75)

    def test_table_interval_and_percentage_points(self):
        rows = [{"t": "200", "a": "3.7", "b": "3.5"}, {"t": "250", "a": "7.7", "b": "6.2"}, {"t": "300", "a": "14.4", "b": "14.7"}]
        result = compare_table(rows, "t", "a", "b", minimum=250)
        self.assertEqual(result["n"], 2)
        self.assertEqual(len(result["counterexamples"]), 1)
        self.assertAlmostEqual(result["counterexamples"][0]["right_minus_left"], -1.5)
        with self.assertRaises(InputError):
            compare_table(rows, "t", "a", "b", minimum=900)


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