"""Rebuild IMM5476 contracts/maps and append its bilingual supplement seed.

Reads actual local templates; never changes the database or sent questionnaires.
"""
import json
from pathlib import Path
import xml.etree.ElementTree as ET

import pikepdf

from prefill_imm_xfa_pdf import find_by_path
from verify_imm5257_pdf import datasets

BASE = Path(__file__).resolve().parents[2]
PREFIX = 'master_entry_level.imm5476.'


def definition():
    rows = []

    def add(id, path, source, en, fr, kind='text', gate='always', optional=False, profile=None):
        row = dict(id=id, xfa=path, source=source if source.startswith('master_') else PREFIX+source,
                   label=en, label_fr=fr, kind=kind, gate=gate, optional=optional)
        if profile:
            row['profile'] = profile
        rows.append(row)

    add('action', 'RadioButtonList', 'action', 'Representative action', 'Action concernant le représentant', 'action')
    add('applicant_family', 'SectionA.familyName', 'master_entry_level.B01', 'Applicant — Family name', 'Demandeur — Nom de famille', 'upper')
    add('applicant_given', 'SectionA.givenName', 'master_entry_level.B02', 'Applicant — Given names', 'Demandeur — Prénoms', 'upper')
    add('applicant_dob', 'SectionA.DOB', 'master_entry_level.B05', 'Applicant — Date of birth', 'Demandeur — Date de naissance', 'date')
    add('applicant_email', 'SectionA.office[0]', 'applicant_email', 'Applicant — Email', 'Demandeur — Courriel', 'email', optional=True)
    add('applicant_contact', 'SectionA.office[1]', 'applicant_alternate_contact', 'Applicant — Telephone or address when no email', 'Demandeur — Téléphone ou adresse sans courriel', gate='no_applicant_email')
    add('application_type', 'SectionA.office[2]', 'application_type', 'Application type', 'Type de demande')
    add('application_number', 'SectionA.application', 'application_number', 'Application number', 'Numéro de demande', gate='has_application_number')
    add('uci', 'SectionA.UCI', 'uci', 'Applicant — UCI', 'Demandeur — IUC', 'uci', gate='has_uci')
    for field,en,fr,profile in [('family_name','Family name','Nom de famille','last_name'),('given_names','Given names','Prénoms','first_name')]:
        add('rep_'+field,'SectionB.'+('familyName' if field=='family_name' else 'givenName'),field,'Representative — '+en,'Représentant — '+fr,'upper','representative',profile=profile)
    add('paid_category','SectionB.question6.questionII.compensated','category','Paid representative — Category','Représentant rémunéré — Catégorie','paid_category','paid')
    add('unpaid_category','SectionB.question6.questionI.uncompensated','category','Unpaid representative — Category','Représentant non rémunéré — Catégorie','unpaid_category','unpaid')
    for mode,section in [('paid','questionII'),('unpaid','questionI')]:
        for category,field in [('cicc','ICCRCMember' if mode=='paid' else 'membership[0]'),('law','membership' if mode=='paid' else 'membership2'),('notary','quebecLaw' if mode=='paid' else 'membership[1]')]:
            category_fr={'cicc':'CCIC','law':'barreau','notary':'notaire'}[category]
            add(mode+'_'+category+'_membership','SectionB.question6.'+section+'.'+field,'membership_number','Representative — Membership number ('+mode+', '+category+')','Représentant — Numéro de membre ('+('rémunéré' if mode=='paid' else 'non rémunéré')+', '+category_fr+')','identifier',mode+'_'+category,profile='license_number')
        add(mode+'_province','SectionB.question6.'+section+'.'+('province' if mode=='paid' else 'province2'),'law_province','Law society — Province or territory','Barreau — Province ou territoire',gate=mode+'_law_province')
    add('other','SectionB.question6.questionI.otherRep','other_description','Unpaid representative — Other category details','Représentant non rémunéré — Précisions sur autre catégorie',gate='unpaid_other')
    contact = [('organization','organization','Organization','Organisme','organization_name',True),('unit','unit','Apartment/unit','Appartement/unité','address_line2',True),('street_number','streetNo','Street number','Numéro de rue','address_line1',True),('street_name','streetName','Street name','Nom de rue','address_line1',False),('city','city','City/town','Ville','city',False),('province','province','Province/state/territory','Province/État/territoire','province',True),('country','country','Country or territory','Pays ou territoire','country',False),('postal_code','postalcode','Postal code/ZIP','Code postal/ZIP','postal_code',True),('phone','phoneCountryCode','Telephone — Country code','Téléphone — Indicatif','phone',False),('phone','phoneNumber','Telephone — Number','Téléphone — Numéro','phone',False),('fax','faxCountryCode','Fax — Country code','Télécopieur — Indicatif',None,True),('fax','faxNumber','Fax — Number','Télécopieur — Numéro',None,True),('email','email','Email','Courriel','email',True)]
    for source,field,en,fr,profile,optional in contact:
        kind = 'country' if source=='country' else ('phone_code' if field.endswith('CountryCode') else 'phone_number' if source in ['phone','fax'] else 'email' if source=='email' else 'text')
        add('contact_'+field,'question7.'+field,source,'Representative — '+en,'Représentant — '+fr,kind,'representative',optional,profile)
    add('supervisor_name','question7.lawyer','supervisor_name','Supervising lawyer — Name','Avocat superviseur — Nom',gate='student')
    add('supervisor_membership','question7.membershipID','supervisor_membership','Supervising lawyer — Membership number','Avocat superviseur — Numéro de membre','identifier','student')
    for group,section,gate in [('previous','sectionC','cancel'),('withdrawing','sectionD','withdraw')]:
        for field,en,fr in [('family_name','Family name','Nom de famille'),('given_names','Given names','Prénoms'),('organization','Organization','Organisme')]:
            leaf={'family_name':'familyName','given_names':'givenName','organization':'organization'}[field]
            add(group+'_'+field,section+'.'+leaf,group+'_'+field,('Previous representative' if group=='previous' else 'Withdrawing representative')+' — '+en,('Ancien représentant' if group=='previous' else 'Représentant qui se retire')+' — '+fr,'text' if field=='organization' else 'upper',gate,field=='organization',{'family_name':'last_name','given_names':'first_name','organization':'organization_name'}[field] if group=='withdrawing' else None)
    return rows


def build():
    rows = definition()
    for language,suffix in [('en','e'),('fr','f')]:
        blank = BASE/f'storage/app/imm/imm5476{suffix}.pdf'
        with pikepdf.open(blank) as pdf:
            xfa=pdf.Root.AcroForm.XFA
            template=next(ET.fromstring(xfa[i+1].read_bytes()) for i in range(0,len(xfa),2) if str(xfa[i])=='template')
        def named_tree(node):
            kind=node.tag.split('}')[-1]
            copy=ET.Element(node.get('name') or kind,dict(node.attrib,template_kind=kind))
            copy.text=node.text
            for child in node:copy.append(named_tree(child))
            return copy
        template=named_tree(template)
        mapping=[]
        contract={}
        for original in rows:
            row=dict(original)
            if language=='fr':
                row['xfa']={'applicant_email':'SectionA.UCI[0]','applicant_contact':'SectionA.office[0]','application_type':'SectionA.office[1]','uci':'SectionA.UCI[1]','unpaid_law_membership':'SectionB.question6.questionI.membership[1]','unpaid_notary_membership':'SectionB.question6.questionI.quebecLaw','unpaid_province':'SectionB.question6.questionI.province'}.get(row['id'],row['xfa'])
            assert find_by_path(datasets(str(blank)),row['xfa']),row['xfa']
            nodes=find_by_path(template,row['xfa'])
            assert nodes,row['xfa']
            node=nodes[0]
            meta={'label':row['label_fr'] if language=='fr' else row['label'],'template_path':row['xfa']}
            if node.get('template_kind')=='exclGroup':
                meta['allowed_values']=list(dict.fromkeys(''.join(item.itertext()).strip() for f in node for item in f if item.tag.split('}')[-1]=='items'))
            maxima=[int(n.get('maxChars')) for n in node.iter() if n.get('maxChars') and int(n.get('maxChars'))>0]
            if maxima:meta['max_chars']=min(maxima)
            contract[row['xfa']]=meta
            mapping.append(row)
        countries=json.loads((BASE/f'resources/imm/imm5707-contract.{language}.json').read_text(encoding='utf-8'))['countries']
        (BASE/f'resources/imm/imm5476-contract.{language}.json').write_text(json.dumps({'fields':contract,'countries':countries},indent=2,ensure_ascii=False)+'\n',encoding='utf-8')
        (BASE/f'resources/imm/maps/imm5476{ ".fr" if language=="fr" else ""}.json').write_text(json.dumps({'form':'IMM5476','pdf':blank.name,'fields':mapping},indent=2,ensure_ascii=False)+'\n',encoding='utf-8')
    seed=BASE/'database/data/master_entry_level_questionnaire.json'
    catalog=json.loads(seed.read_text(encoding='utf-8'))
    questions=[]
    def question(key,en,fr,type='short_text',options=None,rule=None):
        q=dict(source_key=PREFIX+key,label=en,label_fr=fr,question_type=type,required_default=False,options=options,field_schema=None,visibility_rule=rule,display_order=len(questions))
        questions.append(q)
        return q
    def opts(values):return [dict(value=v,label=en,label_fr=fr) for v,en,fr in values]
    def rule(key,values):return {'all':[dict(field=PREFIX+key,op='in',value=values)]}
    b=rule('action',['appoint','update','replace'])
    question('action','IMM5476 — What action are you requesting?','IMM5476 — Quelle action demandez-vous?','single_select',opts([('appoint','Appoint a representative','Désigner un représentant'),('update','Update representative contact details','Mettre à jour les coordonnées'),('cancel','Cancel an appointment','Annuler une désignation'),('replace','Cancel and appoint a new representative','Annuler et désigner un nouveau représentant'),('withdraw','Representative withdrawal','Retrait du représentant'),('unsure','Unsure — consultant review needed','Incertain — examen nécessaire'),('not_required','IMM5476 not required','IMM5476 non requis')]))
    question('use_assigned_profile','Use the assigned consultant profile for this representative?','Utiliser le profil du consultant affecté pour ce représentant?','yes_no',rule=rule('action',['appoint','update','replace','withdraw']))
    question('payment','Is this representative paid or unpaid?','Ce représentant est-il rémunéré ou non?','single_select',opts([('paid','Paid','Rémunéré'),('unpaid','Unpaid','Non rémunéré')]),b)
    question('category','Exact representative category','Catégorie exacte du représentant','single_select',opts([('cicc','CICC member','Membre du CCIC'),('lawyer','Provincial/territorial lawyer','Avocat d’un barreau provincial/territorial'),('student_at_law','Student-at-law','Stagiaire en droit'),('quebec_notary','Quebec notary','Notaire du Québec'),('friend_family','Friend or family member (unpaid)','Ami ou membre de la famille (non rémunéré)'),('other','Other (unpaid)','Autre (non rémunéré)')]),{'all':b['all']+[dict(field=PREFIX+'payment',op='in',value=['paid','unpaid'])]})
    question('has_uci','Has IRCC issued you a UCI?','IRCC vous a-t-il attribué un IUC?','yes_no')
    question('has_application_number','Do you have a number for this application?','Avez-vous un numéro pour cette demande?','yes_no')
    question('membership_applicable','Do you have a law society membership number?','Avez-vous un numéro de membre du barreau?','yes_no',rule={'all':b['all']+[dict(field=PREFIX+'category',op='in',value=['lawyer','student_at_law'])]})
    seen={q['source_key'] for q in questions}
    for row in rows:
        key=row['source'].removeprefix(PREFIX)
        if not row['source'].startswith(PREFIX) or row['source'] in seen:continue
        seen.add(row['source'])
        gate=row['gate'];visibility=None
        if gate in ['representative','paid','unpaid'] or gate.startswith(('paid_','unpaid_')) or gate=='student':visibility=b
        if gate=='cancel':visibility=rule('action',['cancel','replace'])
        if gate=='withdraw':visibility=rule('action',['withdraw'])
        if gate in ['has_uci','has_application_number']:visibility=rule(gate,['yes'])
        # Membership and law fields have their own type parent, preventing stale hidden answers.
        if key in ['membership_number','law_province','supervisor_name','supervisor_membership','other_description']:
            cats={'membership_number':['cicc','lawyer','student_at_law','quebec_notary'],'law_province':['lawyer','student_at_law'],'supervisor_name':['student_at_law'],'supervisor_membership':['student_at_law'],'other_description':['other']}[key]
            visibility={'all':b['all']+[dict(field=PREFIX+'category',op='in',value=cats)]}
        q=question(key,row['label'],row['label_fr'],'phone' if key in ['phone','fax'] else 'email' if row['kind']=='email' else 'long_text' if key=='applicant_alternate_contact' else 'short_text',rule=visibility)
        if key=='country':
            q.update(question_type='single_select',options=[],field_schema={'registry':{'key':'countries','selection':'single'}})
    previous=next((s for s in catalog['sections'] if s['source_key']=='master_entry_level.section.imm5476'),None)
    section=dict(source_key='master_entry_level.section.imm5476',title='IMM5476 — Use of a Representative',title_fr='IMM5476 — Recours aux services d’un représentant',slug='imm5476',display_order=previous['display_order'] if previous else len(catalog['sections']),questions=questions)
    catalog['sections']=[s for s in catalog['sections'] if s['source_key']!=section['source_key']]+[section]
    catalog['section_count']=len(catalog['sections']);catalog['question_count']=sum(len(s['questions']) for s in catalog['sections'])
    seed.write_text(json.dumps(catalog,indent=4,ensure_ascii=False)+'\n',encoding='utf-8')
    print(json.dumps({'mapped_targets':len(rows),'supplement_questions':len(questions)}))


if __name__=='__main__':build()
