"""Schedule A assets and isolated writer acceptance gates."""
import hashlib
import json
from pathlib import Path
import tempfile
import unittest
from lxml import etree as E
import pikepdf

BASE = Path(__file__).resolve().parents[2]


class Imm5669Test(unittest.TestCase):
    def test_choice_groups_never_receive_illegal_saved_value_children(self):
        from imm5669_pdf import fill_schedule_a, dataset_node
        for lang, suffix in [('en', 'e'), ('fr', 'f')]:
            contract = json.loads((BASE/f'resources/imm/imm5669-contract.{lang}.json').read_text(encoding='utf8'))
            choices = {path: meta for path, meta in contract['fields'].items()
                       if meta['kinds'][-1] == 'exclGroup'}
            self.assertEqual(len(choices), 12)
            for export in ('1', '2'):
                with tempfile.TemporaryDirectory(dir=BASE/'storage/app/imm/tmp') as work:
                    output = Path(work)/'choices.pdf'
                    fill_schedule_a({'form': 'IMM5669', 'language': lang,
                                     'fields': dict.fromkeys(choices, export)}, output,
                                    BASE/f'storage/app/imm/imm5669{suffix}.pdf')
                    with pikepdf.open(output) as pdf:
                        x = pdf.Root.AcroForm.XFA
                        packets = {str(x[i]): x[i+1].read_bytes() for i in range(0,len(x),2)}
                    form, data = E.fromstring(packets['form']), E.fromstring(packets['datasets'])
                    self.assertEqual(form.xpath('//*[local-name()="exclGroup"]/*[local-name()="value"]'), [],
                                     'Adobe rejects value as a child of exclGroup')
                    for path, meta in choices.items():
                        self.assertEqual(dataset_node(data, meta['data_path']).text, export)

    def test_contracts_and_standalone_bilingual_questionnaire(self):
        seed = json.loads((BASE/'database/data/schedule_a_hub_questionnaire.json').read_text(encoding='utf8'))
        self.assertEqual(seed['questionnaire_key'], 'schedule_a_hub')
        questions = [q for s in seed['sections'] for q in s['questions']]
        self.assertEqual(seed['question_count'], len(questions))
        self.assertEqual(len({q['source_key'] for q in questions}), len(questions))
        for q in questions:
            self.assertTrue(q['source_key'].startswith('schedule_a_hub.imm5669.'))
            self.assertTrue(q['label'] and q['label_fr'])
            self.assertLessEqual(len(q['label']),255)
            self.assertLessEqual(len(q['label_fr']),500)
            if '.declaration_' in q['source_key']:
                self.assertTrue(q['help_text'] and q['help_text_fr'])
        for lang, suffix in [('en','e'),('fr','f')]:
            contract = json.loads((BASE/f'resources/imm/imm5669-contract.{lang}.json').read_text(encoding='utf8'))
            self.assertEqual(contract['sha256'], hashlib.sha256((BASE/f'storage/app/imm/imm5669{suffix}.pdf').read_bytes()).hexdigest())
            self.assertEqual(contract['root'], 'IMM_5669')
            self.assertEqual(len(contract['tables']['education']['rows']), 5)
            self.assertEqual(len(contract['tables']['addresses']['rows']), 10)

    def test_writer_creates_packets_and_preserves_original_template(self):
        from imm5669_pdf import fill_schedule_a
        for lang, suffix in [('en','e'),('fr','f')]:
            contract = json.loads((BASE/f'resources/imm/imm5669-contract.{lang}.json').read_text(encoding='utf8'))
            fields = {path: ('1' if meta['kind'] in ('role','declaration') else 'SMITH')
                      for path,meta in contract['fields'].items() if meta['source'] in ('role','family_name','declaration_a')}
            blank = BASE/f'storage/app/imm/imm5669{suffix}.pdf'
            with tempfile.TemporaryDirectory(dir=BASE/'storage/app/imm/tmp') as work:
                output = Path(work)/'draft.pdf'
                fill_schedule_a({'form':'IMM5669','language':lang,'fields':fields},output,blank)
                self.assertTrue(output.read_bytes().startswith(blank.read_bytes()))
                def packets(path):
                    with pikepdf.open(path) as p:
                        x=p.Root.AcroForm.XFA
                        return {str(x[i]):x[i+1].read_bytes() for i in range(0,len(x),2)}
                original, generated = packets(blank), packets(output)
                self.assertEqual(original['template'],generated['template'])
                for name, raw in original.items():
                    self.assertEqual(raw, generated[name], name)
                self.assertIn('datasets',generated)
                self.assertIn('form',generated)
                from imm5669_pdf import dataset_node
                data=E.fromstring(generated['datasets'])
                for path,value in fields.items():
                    self.assertEqual(dataset_node(data,contract['fields'][path]['data_path']).text,value)
                with self.assertRaisesRegex(ValueError,'Unallowlisted'):
                    fill_schedule_a({'form':'IMM5669','language':lang,'fields':{'IMM_5669.page4.signatureApplicant':'fake'}},Path(work)/'invalid.pdf',blank)

    def test_all_allowlisted_targets_and_saved_values_match_in_both_languages(self):
        from imm5669_pdf import fill_schedule_a, dataset_node
        from imm0008_xfa_checksum import form_checksum
        for lang,suffix in [('en','e'),('fr','f')]:
            contract=json.loads((BASE/f'resources/imm/imm5669-contract.{lang}.json').read_text(encoding='utf8'))
            fields={path:('2' if meta['kind'] in ('role','declaration') else '1990-01-01' if meta['kind']=='date' else '2010-01' if meta['kind']=='month' else '4' if meta['kind']=='years' else 'Confirmed detail') for path,meta in contract['fields'].items()}
            with tempfile.TemporaryDirectory(dir=BASE/'storage/app/imm/tmp') as work:
                blank=BASE/f'storage/app/imm/imm5669{suffix}.pdf'
                output=Path(work)/'all.pdf'
                payload={'form':'IMM5669','language':lang,'fields':fields}
                fill_schedule_a(payload,output,blank)
                with pikepdf.open(output) as pdf:
                    x=pdf.Root.AcroForm.XFA
                    packets={str(x[i]):x[i+1].read_bytes() for i in range(0,len(x),2)}
                data,form=E.fromstring(packets['datasets']),E.fromstring(packets['form'])
                self.assertEqual(form.get('checksum'),form_checksum(packets['template'],packets['datasets']))
                for path,value in fields.items():
                    meta=contract['fields'][path]
                    self.assertEqual(value,dataset_node(data,meta['data_path']).text,path)
                    saved=dataset_node(form,meta['template_path'],kinds=meta['kinds'])
                    if meta['kinds'][-1] == 'exclGroup':
                        self.assertEqual(saved.xpath('./*[local-name()="value"]'), [], path)
                    else:
                        self.assertEqual(value,saved.xpath('string(./*[local-name()="value"]/*[1])'),path)
                with self.assertRaisesRegex(ValueError,'export'):
                    role=next(p for p,m in contract['fields'].items() if m['kind']=='role')
                    fill_schedule_a({**payload,'fields':{role:'Y'}},Path(work)/'bad.pdf',blank)
                with self.assertRaisesRegex(ValueError,'overwrite'):
                    fill_schedule_a(payload,blank,blank)
