import json
import tempfile
import unittest
import zipfile
from pathlib import Path
from openpyxl import load_workbook
from compare import run
from verify import verify
from example import create


class ComparisonTests(unittest.TestCase):
    def setUp(self):
        self.temp=tempfile.TemporaryDirectory(); self.addCleanup(self.temp.cleanup)
        self.root=Path(self.temp.name); self.source=self.root/'input'; self.config=create(self.source); self.out=self.root/'output'
    def edit(self,vendor,cell,value,sheet='主报价'):
        p=self.source/f'vendor-{vendor}.xlsx'; w=load_workbook(p); w[sheet][cell]=value; w.save(p)
    def generate(self):
        run(self.config,self.source,self.out)
        return verify(self.config,self.source,self.out)
    def test_full_synthetic(self):
        r=self.generate(); self.assertEqual((r['categories'],r['sources'],r['rows']),(2,6,9))
        w=load_workbook(self.out/'蔬菜.xlsx'); ws=w.active
        self.assertEqual(ws['D2'].value,'示例甲'); self.assertEqual(ws['I2'].value,2)
        self.assertIsNone(ws['G5'].value); self.assertIn('无报价',ws['K5'].value)
        self.assertEqual(ws['R2'].value,2); self.assertEqual(ws['J2'].value,2)
        self.assertEqual(load_workbook(self.out/'猪肉.xlsx')['最低价统计']['E2'].value,'单家报价')
    def test_category_tie_requires_explicit_decision(self):
        self.edit(2,'D3',2); self.edit(2,'D4',3); self.edit(2,'D5',None); self.edit(2,'D6',2)
        with self.assertRaisesRegex(ValueError,'category tie'): run(self.config,self.source,self.out)
        c=json.loads(self.config.read_text()); c['tie_break']={'蔬菜':'示例乙'}; self.config.write_text(json.dumps(c))
        self.generate()
    def test_blank_spec_and_multiple_files(self):
        for v in range(1,5):
            p=self.source/f'vendor-{v}.xlsx'; w=load_workbook(p)
            for row in w.active.iter_rows(min_row=2):
                if row[0].value=='测试品2': row[2].value=None
            w.save(p)
        from openpyxl import Workbook
        p=self.source/'extra.xlsx'; w=Workbook(); w.active.title='追加'
        w.active.append(['商品','单位','规格','报价','数量','备注']); w.active.append(['追加品','公斤','标准',2,1,None]); w.save(p)
        c=json.loads(self.config.read_text()); src=dict(c['sources'][0]); src.update(file='extra.xlsx',sheet='追加'); c['sources'].append(src); self.config.write_text(json.dumps(c))
        self.generate()
    def test_duplicate_key(self):
        p=self.source/'vendor-1.xlsx'; w=load_workbook(p); w.active.append(['测试品1','公斤','标准',1,2,None]); w.save(p)
        with self.assertRaisesRegex(ValueError,'duplicate'): run(self.config,self.source,self.out)
    def test_quantity_conflict(self):
        self.edit(2,'E2',99)
        with self.assertRaisesRegex(ValueError,'conflicting quantity'): run(self.config,self.source,self.out)
    def test_bad_header(self):
        self.edit(1,'D1','未知')
        with self.assertRaisesRegex(ValueError,'header'): run(self.config,self.source,self.out)
    def test_unmapped_source(self):
        c=json.loads(self.config.read_text()); c['sources']=c['sources'][1:]; self.config.write_text(json.dumps(c))
        with self.assertRaisesRegex(ValueError,'unmapped'): run(self.config,self.source,self.out)
    def test_formula_without_cache(self):
        self.edit(1,'D2','=1+0')
        with self.assertRaisesRegex(ValueError,'cache'): run(self.config,self.source,self.out)
    def test_formula_with_cache(self):
        self.edit(1,'D2','=1+0'); p=self.source/'vendor-1.xlsx'
        with zipfile.ZipFile(p) as z: data={n:z.read(n) for n in z.namelist()}
        data['xl/worksheets/sheet1.xml']=data['xl/worksheets/sheet1.xml'].replace(b'<f>1+0</f><v></v>',b'<f>1+0</f><v>1</v>')
        with zipfile.ZipFile(p,'w') as z:
            for n,b in data.items(): z.writestr(n,b)
        self.generate()
    def test_invalid_prices(self):
        for value in ['待定',-1,'NaN',True,'#VALUE!']:
            with self.subTest(value=value):
                self.edit(1,'D2',value)
                with self.assertRaises(ValueError): run(self.config,self.source,self.out)
    def test_extreme_positive_and_unsupported_source(self):
        self.edit(4,'D2',1000000000)
        self.generate(); self.assertEqual(load_workbook(self.out/'蔬菜.xlsx').active['O2'].value,1000000000)
        (self.source/'legacy.xls').write_bytes(b'synthetic unsupported source')
        with self.assertRaisesRegex(ValueError,'unsupported'): run(self.config,self.source,self.root/'new-output')
    def test_no_valid_prices(self):
        for v in range(1,5):
            p=self.source/f'vendor-{v}.xlsx'; w=load_workbook(p)
            for row in w.active.iter_rows(min_row=2): row[3].value=0
            w.save(p)
        self.generate(); self.assertIsNone(load_workbook(self.out/'蔬菜.xlsx').active['D2'].value)
    def test_all_supplier_and_output_mutations_rejected(self):
        self.generate(); p=self.out/'蔬菜.xlsx'; original=p.read_bytes()
        for col in ['L','M','N','O','G','I','J','P','Q','R']:
            with self.subTest(col=col):
                p.write_bytes(original); w=load_workbook(p); w.active[col+'2']=999; w.save(p)
                with self.assertRaises(AssertionError): verify(self.config,self.source,self.out)
        p.write_bytes(original)
        for filename,sheet,cell in [('蔬菜.xlsx','最低价统计','B2'),('汇总.xlsx','分类最低价最多供应商','B2')]:
            path=self.out/filename; old=path.read_bytes(); w=load_workbook(path); w[sheet][cell]=999; w.save(path)
            with self.assertRaises(AssertionError): verify(self.config,self.source,self.out)
            path.write_bytes(old)
    def test_source_changes_rejected(self):
        self.generate(); self.edit(1,'D2',123)
        with self.assertRaises(AssertionError): verify(self.config,self.source,self.out)
    def test_previous_and_text_safety(self):
        c=json.loads(self.config.read_text()); c['sources'][0]['columns'].update(previous='上期',previous_date='日期'); self.config.write_text(json.dumps(c))
        self.edit(1,'G1','上期'); self.edit(1,'H1','日期'); self.edit(1,'G2','2'); self.edit(1,'H2','2026-01-01'); self.edit(1,'F2','=NO_EXECUTION()')
        # Literal supplier notes that start with '=' must be source text, not uncached formulas.
        p=self.source/'vendor-1.xlsx'; w=load_workbook(p); w.active['F2'].data_type='s'; w.save(p)
        self.generate(); w=load_workbook(self.out/'蔬菜.xlsx'); self.assertEqual(w.active['H2'].value,-0.5)

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