"""IMM5708 contact state regression checks; does not run Adobe Reader."""
import json
import hashlib
import re
from pathlib import Path
import tempfile
import unittest
import xml.etree.ElementTree as ET

import pikepdf
from prefill_imm_xfa_pdf import fill
from imm5708_pdf_state import contact_phone_state, contact_address_state, address_values, ADDRESS_SELECTION

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


def raw_packets(path):
    with pikepdf.open(path) as pdf:
        xfa = pdf.Root.AcroForm.XFA
        return {str(xfa[i]): xfa[i + 1].read_bytes() for i in range(0, len(xfa), 2)
                if str(xfa[i]) in {'template', 'form', 'datasets'}}


def packets(path):
    return {name: ET.fromstring(data) for name, data in raw_packets(path).items()}


def named_nodes(root):
    result = {}
    def visit(node, path=''):
        if node.get('name'):
            path = path + '.' + node.get('name')
            result[path.lstrip('.')] = node
        for child in node:
            visit(child, path)
    visit(root)
    return result


class Imm5708ContactStateTest(unittest.TestCase):
    def test_address_normalization_does_not_mutate_answers_or_guess_selection(self):
        fields = {ADDRESS_SELECTION: 'Y', 'ContactInformation.Resi.AddrLine2.Country': '511'}
        before = dict(fields)
        normalized = address_values(fields)
        self.assertEqual(before, fields)
        self.assertEqual('', normalized['ContactInformation.Resi.AddrLine2.Country'])
        self.assertEqual('', normalized['ContactInformation.Resi.AddrLine2.District'])
        for choice in [None, '', 'unknown']:
            fields[ADDRESS_SELECTION] = choice
            self.assertEqual(fields, address_values(fields))
            self.assertEqual(b'unchanged', contact_address_state(b'unchanged', fields))

    def test_address_selection_and_residential_state_in_generated_pdfs(self):
        selection = 'ContactInformation.Resi.SameAsAddr.SameAsMailingInd'
        residential = 'ContactInformation.Resi.'
        address = {'AddrLine1.AptUnit': '4', 'AddrLine1.StreetNum': '123',
                   'AddrLine1.Streetname': 'Queen Street', 'AddrLine2.City': 'Toronto',
                   'AddrLine2.Country': '511', 'AddrLine2.Prov': '06',
                   'AddrLine2.PostalCode': 'M5V 2T6', 'AddrLine2.District': 'Toronto'}
        for suffix in ['e', 'f']:
            blank = BASE / f'storage/app/imm/imm5708{suffix}.pdf'
            original_hash = hashlib.sha256(blank.read_bytes()).hexdigest()
            original_raw = raw_packets(blank)
            original = {name: ET.fromstring(data) for name, data in original_raw.items()}
            for choice in ['Y', 'N', None]:
                with self.subTest(language=suffix, choice=choice):
                    fields = {residential + k: v for k, v in address.items()}
                    fields['ComingIntoCda.OrigEntry.Place'] = 'Toronto Pearson Airport'
                    if choice:
                        fields[selection] = choice
                    with tempfile.TemporaryDirectory() as temp:
                        payload, output = Path(temp) / 'payload.json', Path(temp) / 'draft.pdf'
                        payload.write_text(json.dumps({'form': 'IMM5708', 'fields': fields}), encoding='utf-8')
                        fill(str(payload), str(output), str(blank))
                        written_raw = raw_packets(output)
                        written = {name: ET.fromstring(data) for name, data in written_raw.items()}
                        for name in ['template', 'form']:
                            self.assertEqual(re.findall(br'<\?.*?\?>', original_raw[name], re.S),
                                             re.findall(br'<\?.*?\?>', written_raw[name], re.S))
                    from prefill_imm_xfa_pdf import find_by_path, XFA_DATA_NS
                    node = find_by_path(written['datasets'], selection)[0]
                    if choice:
                        self.assertEqual(choice, node.text)
                        self.assertEqual('dataValue', node.get(f'{{{XFA_DATA_NS}}}dataNode'))
                    else:
                        self.assertNotIn(node.text, ['Y', 'N'])
                    self.assertEqual([], list(node))
                    self.assertEqual('Toronto Pearson Airport', find_by_path(written['datasets'], 'ComingIntoCda.OrigEntry.Place')[0].text)
                    for packet in ['template', 'form']:
                        nodes = named_nodes(written[packet])
                        prefix = 'form1.Page2.'
                        if choice:
                            group = nodes[prefix + selection]
                            value = next(c for c in group if c.tag.endswith('value'))
                            self.assertEqual(choice, next(iter(value)).text)
                            for member, on in [('Yes', 'Y'), ('No', 'N')]:
                                member_node = nodes[prefix + selection + '.' + member]
                                member_values = [n.text for c in member_node if c.tag.endswith('value') for n in c]
                                self.assertEqual([on] if choice == on else [None], member_values)
                            for key in address:
                                field = nodes[prefix + residential + key]
                                expected_access = 'readOnly' if choice == 'Y' or key.endswith('Country') else 'open'
                                self.assertEqual(expected_access, field.get('access'))
                                validate = next(c for c in field if c.tag.endswith('validate'))
                                required = choice == 'N' and not key.endswith(('AptUnit', 'StreetNum', 'District'))
                                self.assertEqual('error' if required else 'disabled', validate.get('nullTest'))
                    for key, value in address.items():
                        node = find_by_path(written['datasets'], residential + key)[0]
                        self.assertEqual('' if choice == 'Y' else value, node.text or '')
                    scripts = lambda root: [n.text for n in root.iter() if n.tag.endswith('script')]
                    self.assertEqual(scripts(original['template']), scripts(written['template']))
                    self.assertEqual(scripts(original['form']), scripts(written['form']))
                    self.assertEqual(original_hash, hashlib.sha256(blank.read_bytes()).hexdigest())

    def test_packet_structure_namespaces_and_processing_instructions_are_preserved(self):
        xml = b'<?xml version="1.0"?><?xfa original?><template xmlns="http://www.xfa.org/schema/xfa-template/2.8/" xmlns:x="urn:keep"><subform name="form1"><subform name="Page2"><subform name="ContactInformation"><subform name="q3-4"><subform name="Phone"><subform name="NANumber" presence="invisible"/><subform name="IntlNumber"/><script><![CDATA[if (x < 2) x++;]]></script></subform></subform></subform></subform></subform></template>'
        fields = {'ContactInformation.q3-4.Phone.CanOtherInd.CanadaUS': '1', 'ContactInformation.q3-4.Phone.CanOtherInd.Other': '0'}
        expected = xml.replace(b'name="NANumber" presence="invisible"', b'name="NANumber" presence="visible"').replace(b'name="IntlNumber"/>', b'name="IntlNumber" presence="invisible"/>')
        self.assertEqual(expected, contact_phone_state(xml, fields))

    def test_phone_branches_match_selected_country_and_keep_official_scripts(self):
        for lang, suffix in [('en', 'e'), ('fr', 'f')]:
            for primary_nanp, alternate_nanp in [(True, False), (False, True)]:
                with self.subTest(lang=lang, primary_nanp=primary_nanp):
                    fields = {}
                    for group, nanp in [('Phone', primary_nanp), ('AltPhone', alternate_nanp)]:
                        prefix = 'ContactInformation.q3-4.' + group + '.'
                        fields[prefix + 'CanOtherInd.CanadaUS'] = '1' if nanp else '0'
                        fields[prefix + 'CanOtherInd.Other'] = '0' if nanp else '1'
                        fields[prefix + 'NumberCountry'] = '1' if nanp else '86'
                    blank = BASE / f'storage/app/imm/imm5708{suffix}.pdf'
                    original = packets(blank)
                    temp_root = BASE / 'storage/app/imm/tmp'
                    temp_root.mkdir(parents=True, exist_ok=True)
                    with tempfile.TemporaryDirectory(dir=temp_root, prefix='contact-state-') as temp:
                        payload = Path(temp) / 'payload.json'
                        output = Path(temp) / 'draft.pdf'
                        payload.write_text(json.dumps({'form': 'IMM5708', 'fields': fields}), encoding='utf-8')
                        fill(str(payload), str(output), str(blank))
                        written = packets(output)
                    for packet in ['template', 'form']:
                        nodes = named_nodes(written[packet])
                        for group, nanp in [('Phone', primary_nanp), ('AltPhone', alternate_nanp)]:
                            prefix = 'form1.Page2.ContactInformation.q3-4.' + group + '.'
                            self.assertEqual('visible' if nanp else 'invisible', nodes[prefix + 'NANumber'].get('presence', 'visible'))
                            self.assertEqual('invisible' if nanp else 'visible', nodes[prefix + 'IntlNumber'].get('presence', 'visible'))
                    scripts = lambda root: [n.text for n in root.iter() if n.tag.endswith('script')]
                    self.assertEqual(scripts(original['template']), scripts(written['template']))
                    # No answer guessed for missing residential-address confirmation.
                    self.assertFalse(any(n.text in ['Y', 'N'] for n in written['datasets'].iter() if n.tag.endswith('SameAsMailingInd')))


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