import csv
import tempfile
import unittest
from pathlib import Path
import matcher

def item(**changes):
    row = dict(code='A100', unit='each', description='Synthetic valve', approved_unit_price_dkk='12.50', price_source='SYNTHETIC approved list v1', code_key='a100', unit_key='each')
    row.update(changes)
    return row

def request(**changes):
    row = dict(line='1', code='A100', description='Synthetic valve', quantity='3', unit='each')
    row.update(changes)
    return row

class MatcherTests(unittest.TestCase):
    def check_hold(self, req, catalogue, reason):
        row = matcher.match_row(req, catalogue)
        self.assertEqual(row['status'], 'HUMAN_REVIEW')
        self.assertEqual(row['review_reason'], reason)
        self.assertEqual(row['line_total_dkk'], '')
        self.assertEqual(row['approved_unit_price_dkk'], '')

    def test_exact_match(self):
        row = matcher.match_row(request(), {'a100': [item()]})
        self.assertEqual(row['status'], 'READY_FOR_QUOTE_DRAFT')
        self.assertEqual(row['approved_unit_price_dkk'], '12.50')
        self.assertEqual(row['line_total_dkk'], '37.50')
        self.assertEqual(row['price_source'], 'SYNTHETIC approved list v1')

    def test_unknown_no_description_substitution(self):
        self.check_hold(request(code='X'), {'a100': [item()]}, 'UNKNOWN_CODE')

    def test_unit_mismatch(self):
        self.check_hold(request(unit='box'), {'a100': [item()]}, 'INCOMPATIBLE_UNIT')

    def test_ambiguity(self):
        self.check_hold(request(), {'a100': [item(), item()]}, 'AMBIGUOUS_CODE_AND_UNIT')

    def test_invalid_quantities(self):
        for value in ['0', '-1', 'NaN', 'Infinity', 'bad', '']:
            with self.subTest(value=value):
                self.check_hold(request(quantity=value), {'a100': [item()]}, 'INVALID_QUANTITY')

    def test_missing_price(self):
        self.check_hold(request(), {'a100': [item(approved_unit_price_dkk='')]}, 'MISSING_APPROVED_PRICE')

    def test_invalid_prices(self):
        for value in ['-1', 'NaN', 'Infinity', 'bad']:
            with self.subTest(value=value):
                self.check_hold(request(), {'a100': [item(approved_unit_price_dkk=value)]}, 'INVALID_APPROVED_PRICE')

    def test_missing_provenance(self):
        self.check_hold(request(), {'a100': [item(price_source='')]}, 'MISSING_PRICE_PROVENANCE')

    def test_fractional_quantity_round_half_up(self):
        row = matcher.match_row(request(quantity='0.5'), {'a100': [item(approved_unit_price_dkk='0.05')]})
        self.assertEqual(row['line_total_dkk'], '0.03')

    def test_overprecision_price_is_held(self):
        self.check_hold(request(quantity='100'), {'a100': [item(approved_unit_price_dkk='1.005')]}, 'UNSUPPORTED_PRICE_PRECISION')

    def test_extreme_total_is_held(self):
        self.check_hold(request(quantity='1e999999'), {'a100': [item()]}, 'ARITHMETIC_RANGE')

    def test_blank_code_is_held_even_if_catalogue_has_blank(self):
        self.check_hold(request(code=''), {'': [item(code='', code_key='')]}, 'MISSING_CODE_OR_UNIT')

    def test_csv_formula_prefix(self):
        for value in ['=1+1', '+cmd', '-cmd', '@SUM(A1)', '  =1']:
            self.assertTrue(matcher.safe_csv(value).startswith("'"))

    def test_end_to_end(self):
        root = Path(__file__).parent
        with tempfile.TemporaryDirectory() as tmp:
            output = Path(tmp) / 'draft.csv'
            rows = matcher.run(root / 'request.csv', root / 'catalogue.csv', output)
            self.assertEqual(len(rows), 7)
            self.assertEqual([r['line_total_dkk'] for r in rows], ['37.50', '', '', '', '', '', '0.03'])
            with output.open(newline='') as source:
                written = list(csv.DictReader(source))
            self.assertEqual(written[1]['requested_description'], "'=SYNTHETIC()")

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