import unittest
from decimal import Decimal
from observe import bind_value, encoded, observe


class ImportTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.result = observe()
        cls.rows = {(r["source_lexeme"], r["transport"], r["schema"]): r for r in cls.result["rows"]}

    def row(self, lexeme, transport, schema):
        return self.rows[(lexeme, transport, schema)]

    def test_matrix_is_complete_and_each_attempt_is_accounted(self):
        self.assertEqual(len(self.rows), 75)
        for row in self.rows.values():
            self.assertNotEqual(row["error"] is None, row["stored"] is None)
            if row["error"]:
                self.assertEqual(row["error"]["code"], 3091)

    def test_decimal_oracle_and_integer_path(self):
        for lexeme in ("9007199254740993", "9007199254740993.0", "9.007199254740993e15"):
            row = self.row(lexeme, "exact_integer", "strict_integer")
            self.assertEqual(row["stored"]["value"], "9007199254740993")
            self.assertTrue(row["numeric_equals_source"])
        self.assertEqual(bind_value("9223372036854775807", "exact_integer"), 2**63-1)
        for lexeme in ("1.5", "9223372036854775808", "NaN"):
            with self.assertRaises(ValueError):
                bind_value(lexeme, "exact_integer")

    def test_decimal_text_can_become_a_different_integer(self):
        for schema in ("ordinary_numeric", "ordinary_any", "strict_integer"):
            row = self.row("9007199254740993.0", "text", schema)
            self.assertEqual(row["stored"]["value"], "9007199254740992")
            self.assertFalse(row["numeric_equals_source"])
        self.assertTrue(self.row("9007199254740993", "text", "strict_integer")["numeric_equals_source"])

    def test_any_preserves_received_type_not_lost_input(self):
        for lexeme in ("00123", "9007199254740993.0"):
            self.assertTrue(self.row(lexeme, "text", "strict_any")["lexeme_equals_source"])
        self.assertEqual(self.row("00123", "text", "ordinary_any")["stored"]["value"], "123")
        row = self.row("9007199254740993", "binary64", "strict_any")
        self.assertEqual(row["stored"]["storage_class"], "real")
        self.assertFalse(row["numeric_equals_source"])
        self.assertEqual(Decimal(row["bound"]["exact_decimal"]), Decimal("9007199254740992"))

    def test_backup_and_checks_do_not_restore_source_digits(self):
        self.assertEqual(self.result["source_sha256_before"], self.result["source_sha256_after"])
        self.assertTrue(self.result["backup"]["matrix_equal_after_reopen"])
        for check in (self.result["source_checks"], self.result["backup"]["checks"]):
            self.assertEqual(check, {"quick_check": ["ok"], "integrity_check": ["ok"]})
        self.assertEqual(self.result["backup"]["progress"][-1]["status"], 101)
        self.assertEqual(self.result["backup"]["progress"][-1]["remaining"], 0)
        for control in self.result["collision_controls"]:
            self.assertEqual(control["reopened_rows"], self.result["backup"]["collision_rows"][control["table"]])

    def test_collision_and_rejection_controls(self):
        integer, any_type = self.result["collision_controls"]
        self.assertEqual(integer["events"][1]["error"]["name"], "SQLITE_CONSTRAINT_UNIQUE")
        self.assertEqual(len(integer["reopened_rows"]), 1)
        self.assertEqual(len(any_type["reopened_rows"]), 2)
        self.assertTrue(all(c["error"]["code"] == 3091
                            for c in self.result["rejection_controls"]))

    def test_evidence_encoding_never_uses_a_json_number_for_large_values(self):
        self.assertEqual(encoded(2**53+1)["value"], "9007199254740993")
        self.assertEqual(encoded(float(2**53+1))["exact_decimal"], "9007199254740992")
        with self.assertRaises(ValueError):
            bind_value("1", "unknown")
        for row in self.rows.values():
            self.assertIsInstance(row["bound"]["value"], str)
            if row["stored"]:
                self.assertIsInstance(row["stored"]["value"], str)


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