import io
import unittest
from decimal import Decimal
from csv_validator import iter_records

HEADER = "id,email,amount,status\n"
GOOD = "1,ops@example.com,12.30,active\n"


class CsvTests(unittest.TestCase):
    def parse(self, text, **kw):
        return list(iter_records(io.StringIO(text), **kw))

    def reject(self, text, **kw):
        with self.assertRaises(ValueError):
            self.parse(text, **kw)

    def test_decimal_precision(self):
        self.assertEqual(self.parse(HEADER + GOOD)[0]["amount"], Decimal("12.30"))

    def test_reordered_trimmed_headers(self):
        row = self.parse(" status ,amount,email,id\npending,0,ops@example.com,9\n")[0]
        self.assertEqual(row["id"], 9)

    def test_duplicate_header(self):
        self.reject("id,email,amount,status,id\n1,a@b.com,1,active,2\n")

    def test_missing_header(self):
        self.reject("")

    def test_unexpected_header(self):
        self.reject("id,email,amount,other\n")

    def test_missing_and_extra_columns(self):
        for row in ("1,ops@example.com,1\n", "1,ops@example.com,1,active,extra\n"):
            with self.subTest(row=row):
                self.reject(HEADER + row)

    def test_nonfinite_negative_and_excess_precision(self):
        for value in ("NaN", "Infinity", "-1", "1.001", "1e4", "1234567890123"):
            with self.subTest(value=value):
                self.reject(HEADER + f"1,ops@example.com,{value},active\n")

    def test_unicode_digits_rejected(self):
        self.reject(HEADER + "²,ops@example.com,1,active\n")

    def test_formula_like_email_rejected(self):
        self.reject(HEADER + "1,=SUM(A1)@example.com,1,active\n")

    def test_status_rejected(self):
        self.reject(HEADER + "1,ops@example.com,1,unknown\n")

    def test_unclosed_quote(self):
        self.reject(HEADER + '1,"ops@example.com,1,active\n')

    def test_row_limit(self):
        self.reject(HEADER + GOOD * 2, max_rows=1)

    def test_input_and_line_limits(self):
        self.reject(HEADER + GOOD, max_chars=30)
        self.reject(HEADER + GOOD, max_line_chars=25)

    def test_lazy_failure(self):
        rows = iter_records(io.StringIO(HEADER + GOOD + "invalid\n"))
        self.assertEqual(next(rows)["id"], 1)
        with self.assertRaises(ValueError):
            next(rows)

    def test_multiline_and_quotes(self):
        self.assertEqual(self.parse(HEADER + '1,"\nops@example.com",1,active\n')[0]["id"], 1)


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