"""IMM5532-only incremental datasets writer. No template/script/permission edits."""
import hashlib
import json
import os
import re
from pathlib import Path
from lxml import etree as E

PART = re.compile(r'([^\[\].]+)(?:\[(\d+)\])?')


def resolve(root, path):
    node = root
    if E.QName(node).localname == 'datasets':
        node = next(c for c in node if isinstance(c.tag,str) and E.QName(c).localname == 'data')
    for piece in path.split('.'):
        match = PART.fullmatch(piece)
        if not match: raise ValueError('Invalid IMM5532 path')
        nodes = [c for c in node if isinstance(c.tag,str) and E.QName(c).localname == match[1]]
        if match[2] is None and len(nodes) != 1: raise ValueError('Ambiguous IMM5532 path: '+path)
        index = int(match[2] or 0)
        if index >= len(nodes): raise ValueError('Unresolved IMM5532 path: '+path)
        node = nodes[index]
    return node


def fill_relationship(payload, output_path, blank_path):
    if payload.get('form') != 'IMM5532': raise ValueError('IMM5532 writer rejects other forms')
    lang = payload.get('language','en')
    if lang not in ['en','fr']: raise ValueError('Unsupported IMM5532 language')
    c = json.loads(Path(__file__).with_name(f'imm5532-contract.{lang}.json').read_text(encoding='utf-8'))
    blank, out = Path(blank_path), Path(output_path)
    if blank.resolve() == out.resolve(): raise ValueError('Cannot overwrite original template')
    if hashlib.sha256(blank.read_bytes()).hexdigest() != c['sha256']: raise ValueError('IMM5532 template requires review')
    fields = payload.get('fields')
    if fields == []: fields = {}
    if not isinstance(fields,dict): raise ValueError('Invalid IMM5532 fields')
    for key,value in fields.items():
        if key not in c['fields']: raise ValueError('Unallowlisted IMM5532 field: '+str(key))
        meta = c['fields'][key]
        if not isinstance(value,str) or len(value) > meta['max_chars']: raise ValueError('Invalid IMM5532 value: '+key)
        if meta['choices'] and value not in meta['choices'].values(): raise ValueError('Invalid IMM5532 export: '+key)
        if meta.get('numeric') and not re.fullmatch(r'\d+(?:\.\d{1,2})?',value): raise ValueError('Invalid IMM5532 numeric value: '+key)
        if re.search(r'[\x00-\x08\x0b\x0c\x0e-\x1f]',value): raise ValueError('Invalid XML characters')
    # PHP/Symfony can launch Windows children without SystemRoot. asyncio's
    # Windows socket provider needs it before pyHanko is imported.
    if os.name == 'nt' and not os.environ.get('SystemRoot'):
        import ctypes
        buffer = ctypes.create_unicode_buffer(32768)
        size = ctypes.windll.kernel32.GetWindowsDirectoryW(buffer, len(buffer))
        if not size or size >= len(buffer):
            raise OSError('Cannot resolve Windows system directory')
        os.environ['SystemRoot'] = buffer.value
    from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
    from pyhanko.pdf_utils import generic
    from imm0008_xfa_checksum import form_checksum
    with blank.open('rb') as source:
        writer = IncrementalPdfFileWriter(source,strict=False)
        if writer.prev.encrypted:
            writer.encrypt('')
        x = writer.root['/AcroForm']['/XFA']
        packets = {str(x[i]):x[i+1].data for i in range(0,len(x),2)}
        parser = E.XMLParser(resolve_entities=False,remove_comments=False,remove_pis=False)
        data = E.fromstring(packets['datasets'],parser)
        for key,value in fields.items(): resolve(data,key).text=value
        updated = E.tostring(data)
        # Reject discrepancies before producing any downloadable artifact.
        reparsed = E.fromstring(updated,parser)
        if any((resolve(reparsed,key).text or '') != value for key,value in fields.items()):
            raise ValueError('IMM5532 payload/XML mismatch')
        form = E.fromstring(packets['form'],parser)
        if len(form): raise ValueError('IMM5532 saved form state requires review')
        form.set('checksum',form_checksum(packets['template'],updated))
        updates = {'datasets':updated,'form':E.tostring(form)}
        for i in range(0,len(x),2):
            name = str(x[i])
            if name in updates:
                reference=x.raw_get(i+1)
                stream=generic.StreamObject(stream_data=updates[name]);stream.compress()
                writer.objects[(reference.generation,reference.idnum)] = stream
        with out.open('wb') as target: writer.write(target)
    return out
