"""Unattend.xml parser, builder, and form-data extractor.

Round-trips a Windows ``FlatUnattendW10.xml`` between the disk format
and a flat dict the editor template can render. ``parse_unattend()``
reads disk + returns a dict; ``build_unattend_xml()`` takes the dict
+ returns a string for writing back; ``extract_form_data()`` converts
a Flask ``request.form`` MultiDict into the dict format that
``build_unattend_xml()`` expects.
"""

import os

from lxml import etree

import config

UNATTEND_TEMPLATE = """\
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend"
          xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
  <settings pass="windowsPE" />
  <settings pass="offlineServicing" />
  <settings pass="specialize" />
  <settings pass="oobeSystem" />
</unattend>
"""


def qn(tag):
    """Return a tag qualified with the default unattend namespace."""
    return f"{{{config.UNATTEND_NS}}}{tag}"


def qwcm(attr):
    """Return an attribute qualified with the wcm namespace."""
    return f"{{{config.WCM_NS}}}{attr}"


def _find_or_create(parent, tag):
    """Find the first child with *tag* or create it."""
    el = parent.find(tag, namespaces={"": config.UNATTEND_NS})
    if el is None:
        el = etree.SubElement(parent, qn(tag.split("}")[-1]) if "}" not in tag else tag)
    return el


def _settings_pass(root, pass_name):
    """Return the <settings pass="..."> element, creating if needed."""
    for s in root.findall(qn("settings")):
        if s.get("pass") == pass_name:
            return s
    s = etree.SubElement(root, qn("settings"))
    s.set("pass", pass_name)
    return s


def parse_unattend(xml_path):
    """Parse an unattend.xml and return a dict of editable data."""
    data = {
        "driver_paths": [],
        "computer_name": "",
        "registered_organization": "",
        "registered_owner": "",
        "time_zone": "",
        "specialize_commands": [],
        "oobe": {
            "HideEULAPage": "true",
            "HideOEMRegistrationScreen": "true",
            "HideOnlineAccountScreens": "true",
            "HideWirelessSetupInOOBE": "true",
            "HideLocalAccountScreen": "true",
            "NetworkLocation": "Work",
            "ProtectYourPC": "3",
            "SkipUserOOBE": "true",
            "SkipMachineOOBE": "true",
        },
        "firstlogon_commands": [],
        "user_accounts": [],
        "autologon": {
            "enabled": "",
            "username": "",
            "password": "",
            "plain_text": "true",
            "logon_count": "",
        },
        "intl": {
            "input_locale": "",
            "system_locale": "",
            "ui_language": "",
            "user_locale": "",
        },
        "oobe_timezone": "",
        "raw_xml": "",
    }

    if not os.path.isfile(xml_path):
        data["raw_xml"] = UNATTEND_TEMPLATE
        return data

    with open(xml_path, "r", encoding="utf-8") as fh:
        raw = fh.read()
    data["raw_xml"] = raw

    try:
        root = etree.fromstring(raw.encode("utf-8"))
    except etree.XMLSyntaxError:
        return data

    ns = {"u": config.UNATTEND_NS}

    # --- offlineServicing: DriverPaths ---
    for dp_el in root.xpath(
        "u:settings[@pass='offlineServicing']//u:PathAndCredentials/u:Path",
        namespaces=ns,
    ):
        if dp_el.text:
            data["driver_paths"].append(dp_el.text.strip())

    # --- specialize: Shell-Setup ---
    for comp in root.xpath("u:settings[@pass='specialize']/u:component", namespaces=ns):
        comp_name = comp.get("name", "")
        if "Shell-Setup" in comp_name:
            for tag, key in [
                ("ComputerName", "computer_name"),
                ("RegisteredOrganization", "registered_organization"),
                ("RegisteredOwner", "registered_owner"),
                ("TimeZone", "time_zone"),
            ]:
                el = comp.find(qn(tag))
                if el is not None and el.text:
                    data[key] = el.text.strip()

    # --- specialize: RunSynchronous commands ---
    for cmd in root.xpath(
        "u:settings[@pass='specialize']//u:RunSynchronousCommand",
        namespaces=ns,
    ):
        order_el = cmd.find(qn("Order"))
        path_el = cmd.find(qn("Path"))
        desc_el = cmd.find(qn("Description"))
        data["specialize_commands"].append({
            "order": order_el.text.strip() if order_el is not None and order_el.text else "",
            "path": path_el.text.strip() if path_el is not None and path_el.text else "",
            "description": desc_el.text.strip() if desc_el is not None and desc_el.text else "",
        })

    # --- oobeSystem ---
    for comp in root.xpath("u:settings[@pass='oobeSystem']/u:component", namespaces=ns):
        comp_name = comp.get("name", "")

        if "International-Core" in comp_name:
            for tag, key in [
                ("InputLocale", "input_locale"),
                ("SystemLocale", "system_locale"),
                ("UILanguage", "ui_language"),
                ("UserLocale", "user_locale"),
            ]:
                el = comp.find(qn(tag))
                if el is not None and el.text:
                    data["intl"][key] = el.text.strip()

        if "OOBE" in comp_name or "Shell-Setup" in comp_name:
            oobe_el = comp.find(qn("OOBE"))
            if oobe_el is not None:
                for child in oobe_el:
                    local = etree.QName(child).localname
                    if local in data["oobe"] and child.text:
                        data["oobe"][local] = child.text.strip()

            ua = comp.find(qn("UserAccounts"))
            if ua is not None:
                la_container = ua.find(qn("LocalAccounts"))
                if la_container is not None:
                    for acct in la_container.findall(qn("LocalAccount")):
                        name_el = acct.find(qn("Name"))
                        group_el = acct.find(qn("Group"))
                        display_el = acct.find(qn("DisplayName"))
                        pw_el = acct.find(qn("Password"))
                        pw_val = ""
                        pw_plain = "true"
                        if pw_el is not None:
                            v = pw_el.find(qn("Value"))
                            p = pw_el.find(qn("PlainText"))
                            if v is not None and v.text:
                                pw_val = v.text.strip()
                            if p is not None and p.text:
                                pw_plain = p.text.strip()
                        data["user_accounts"].append({
                            "name": name_el.text.strip() if name_el is not None and name_el.text else "",
                            "password": pw_val,
                            "plain_text": pw_plain,
                            "group": group_el.text.strip() if group_el is not None and group_el.text else "Administrators",
                            "display_name": display_el.text.strip() if display_el is not None and display_el.text else "",
                        })

            al = comp.find(qn("AutoLogon"))
            if al is not None:
                enabled_el = al.find(qn("Enabled"))
                user_el = al.find(qn("Username"))
                count_el = al.find(qn("LogonCount"))
                pw_el = al.find(qn("Password"))
                pw_val = ""
                pw_plain = "true"
                if pw_el is not None:
                    v = pw_el.find(qn("Value"))
                    p = pw_el.find(qn("PlainText"))
                    if v is not None and v.text:
                        pw_val = v.text.strip()
                    if p is not None and p.text:
                        pw_plain = p.text.strip()
                data["autologon"] = {
                    "enabled": enabled_el.text.strip() if enabled_el is not None and enabled_el.text else "",
                    "username": user_el.text.strip() if user_el is not None and user_el.text else "",
                    "password": pw_val,
                    "plain_text": pw_plain,
                    "logon_count": count_el.text.strip() if count_el is not None and count_el.text else "",
                }

            flc = comp.find(qn("FirstLogonCommands"))
            if flc is not None:
                for sync in flc.findall(qn("SynchronousCommand")):
                    order_el = sync.find(qn("Order"))
                    cl_el = sync.find(qn("CommandLine"))
                    desc_el = sync.find(qn("Description"))
                    data["firstlogon_commands"].append({
                        "order": order_el.text.strip() if order_el is not None and order_el.text else "",
                        "commandline": cl_el.text.strip() if cl_el is not None and cl_el.text else "",
                        "description": desc_el.text.strip() if desc_el is not None and desc_el.text else "",
                    })

            tz_el = comp.find(qn("TimeZone"))
            if tz_el is not None and tz_el.text:
                data["oobe_timezone"] = tz_el.text.strip()

    return data


def build_unattend_xml(form_data):
    """Build a complete unattend.xml string from form data dict."""
    root = etree.Element(qn("unattend"), nsmap=config.NSMAP)

    _settings_pass(root, "windowsPE")

    # --- offlineServicing: DriverPaths ---
    offline = _settings_pass(root, "offlineServicing")
    driver_paths = form_data.get("driver_paths", [])
    if driver_paths:
        comp = etree.SubElement(offline, qn("component"))
        comp.set("name", "Microsoft-Windows-PnpCustomizationsNonWinPE")
        comp.set("processorArchitecture", "amd64")
        comp.set("publicKeyToken", "31bf3856ad364e35")
        comp.set("language", "neutral")
        comp.set("versionScope", "nonSxS")
        dp_container = etree.SubElement(comp, qn("DriverPaths"))
        for idx, dp in enumerate(driver_paths, start=1):
            if not dp.strip():
                continue
            pac = etree.SubElement(dp_container, qn("PathAndCredentials"))
            pac.set(qwcm("action"), "add")
            pac.set(qwcm("keyValue"), str(idx))
            path_el = etree.SubElement(pac, qn("Path"))
            path_el.text = dp.strip()

    # --- specialize ---
    spec = _settings_pass(root, "specialize")

    shell_comp = etree.SubElement(spec, qn("component"))
    shell_comp.set("name", "Microsoft-Windows-Shell-Setup")
    shell_comp.set("processorArchitecture", "amd64")
    shell_comp.set("publicKeyToken", "31bf3856ad364e35")
    shell_comp.set("language", "neutral")
    shell_comp.set("versionScope", "nonSxS")

    for tag, key in [
        ("ComputerName", "computer_name"),
        ("RegisteredOrganization", "registered_organization"),
        ("RegisteredOwner", "registered_owner"),
        ("TimeZone", "time_zone"),
    ]:
        val = form_data.get(key, "").strip()
        if val:
            el = etree.SubElement(shell_comp, qn(tag))
            el.text = val

    spec_cmds = form_data.get("specialize_commands", [])
    if spec_cmds:
        deploy_comp = etree.SubElement(spec, qn("component"))
        deploy_comp.set("name", "Microsoft-Windows-Deployment")
        deploy_comp.set("processorArchitecture", "amd64")
        deploy_comp.set("publicKeyToken", "31bf3856ad364e35")
        deploy_comp.set("language", "neutral")
        deploy_comp.set("versionScope", "nonSxS")
        rs = etree.SubElement(deploy_comp, qn("RunSynchronous"))
        for idx, cmd in enumerate(spec_cmds, start=1):
            if not cmd.get("path", "").strip():
                continue
            rsc = etree.SubElement(rs, qn("RunSynchronousCommand"))
            rsc.set(qwcm("action"), "add")
            order_el = etree.SubElement(rsc, qn("Order"))
            order_el.text = str(idx)
            path_el = etree.SubElement(rsc, qn("Path"))
            path_el.text = cmd["path"].strip()
            desc_el = etree.SubElement(rsc, qn("Description"))
            desc_el.text = cmd.get("description", "").strip()

    # --- oobeSystem ---
    oobe_settings = _settings_pass(root, "oobeSystem")

    intl = form_data.get("intl", {})
    if any(v.strip() for v in intl.values() if v):
        intl_comp = etree.SubElement(oobe_settings, qn("component"))
        intl_comp.set("name", "Microsoft-Windows-International-Core")
        intl_comp.set("processorArchitecture", "amd64")
        intl_comp.set("publicKeyToken", "31bf3856ad364e35")
        intl_comp.set("language", "neutral")
        intl_comp.set("versionScope", "nonSxS")
        for tag, key in [
            ("InputLocale", "input_locale"),
            ("SystemLocale", "system_locale"),
            ("UILanguage", "ui_language"),
            ("UserLocale", "user_locale"),
        ]:
            val = intl.get(key, "").strip()
            if val:
                el = etree.SubElement(intl_comp, qn(tag))
                el.text = val

    oobe_comp = etree.SubElement(oobe_settings, qn("component"))
    oobe_comp.set("name", "Microsoft-Windows-Shell-Setup")
    oobe_comp.set("processorArchitecture", "amd64")
    oobe_comp.set("publicKeyToken", "31bf3856ad364e35")
    oobe_comp.set("language", "neutral")
    oobe_comp.set("versionScope", "nonSxS")

    oobe_el = etree.SubElement(oobe_comp, qn("OOBE"))
    oobe_data = form_data.get("oobe", {})
    for key in [
        "HideEULAPage",
        "HideOEMRegistrationScreen",
        "HideOnlineAccountScreens",
        "HideWirelessSetupInOOBE",
        "HideLocalAccountScreen",
        "NetworkLocation",
        "ProtectYourPC",
        "SkipUserOOBE",
        "SkipMachineOOBE",
    ]:
        val = oobe_data.get(key, "")
        if val:
            child = etree.SubElement(oobe_el, qn(key))
            child.text = str(val)

    accounts = form_data.get("user_accounts", [])
    if accounts:
        ua = etree.SubElement(oobe_comp, qn("UserAccounts"))
        la_container = etree.SubElement(ua, qn("LocalAccounts"))
        for acct in accounts:
            if not acct.get("name", "").strip():
                continue
            la = etree.SubElement(la_container, qn("LocalAccount"))
            la.set(qwcm("action"), "add")
            pw = etree.SubElement(la, qn("Password"))
            pw_val = etree.SubElement(pw, qn("Value"))
            pw_val.text = acct.get("password", "")
            pw_plain = etree.SubElement(pw, qn("PlainText"))
            pw_plain.text = acct.get("plain_text", "true")
            name_el = etree.SubElement(la, qn("Name"))
            name_el.text = acct["name"].strip()
            group_el = etree.SubElement(la, qn("Group"))
            group_el.text = acct.get("group", "Administrators").strip()
            display_el = etree.SubElement(la, qn("DisplayName"))
            display_el.text = acct.get("display_name", acct["name"]).strip()

    autologon = form_data.get("autologon", {})
    if autologon.get("username", "").strip():
        al = etree.SubElement(oobe_comp, qn("AutoLogon"))
        al_pw = etree.SubElement(al, qn("Password"))
        al_pw_val = etree.SubElement(al_pw, qn("Value"))
        al_pw_val.text = autologon.get("password", "")
        al_pw_plain = etree.SubElement(al_pw, qn("PlainText"))
        al_pw_plain.text = autologon.get("plain_text", "true")
        al_enabled = etree.SubElement(al, qn("Enabled"))
        al_enabled.text = autologon.get("enabled", "true")
        al_user = etree.SubElement(al, qn("Username"))
        al_user.text = autologon["username"].strip()
        logon_count = autologon.get("logon_count", "").strip()
        if logon_count:
            al_count = etree.SubElement(al, qn("LogonCount"))
            al_count.text = logon_count

    fl_cmds = form_data.get("firstlogon_commands", [])
    if fl_cmds:
        flc = etree.SubElement(oobe_comp, qn("FirstLogonCommands"))
        for idx, cmd in enumerate(fl_cmds, start=1):
            if not cmd.get("commandline", "").strip():
                continue
            sc = etree.SubElement(flc, qn("SynchronousCommand"))
            sc.set(qwcm("action"), "add")
            order_el = etree.SubElement(sc, qn("Order"))
            order_el.text = str(idx)
            cl_el = etree.SubElement(sc, qn("CommandLine"))
            cl_el.text = cmd["commandline"].strip()
            desc_el = etree.SubElement(sc, qn("Description"))
            desc_el.text = cmd.get("description", "").strip()

    oobe_tz = form_data.get("oobe_timezone", "").strip()
    if oobe_tz:
        tz_el = etree.SubElement(oobe_comp, qn("TimeZone"))
        tz_el.text = oobe_tz

    xml_bytes = etree.tostring(
        root,
        pretty_print=True,
        xml_declaration=True,
        encoding="utf-8",
    )
    return xml_bytes.decode("utf-8")


def extract_form_data(form):
    """Pull structured data from the submitted form."""
    data = {}

    dp_list = form.getlist("driver_path[]")
    data["driver_paths"] = [p for p in dp_list if p.strip()]

    data["computer_name"] = form.get("computer_name", "")
    data["registered_organization"] = form.get("registered_organization", "")
    data["registered_owner"] = form.get("registered_owner", "")
    data["time_zone"] = form.get("time_zone", "")

    spec_paths = form.getlist("spec_cmd_path[]")
    spec_descs = form.getlist("spec_cmd_desc[]")
    data["specialize_commands"] = []
    for i in range(len(spec_paths)):
        if spec_paths[i].strip():
            data["specialize_commands"].append({
                "path": spec_paths[i],
                "description": spec_descs[i] if i < len(spec_descs) else "",
            })

    data["oobe"] = {}
    for key in [
        "HideEULAPage",
        "HideOEMRegistrationScreen",
        "HideOnlineAccountScreens",
        "HideWirelessSetupInOOBE",
        "HideLocalAccountScreen",
        "SkipUserOOBE",
        "SkipMachineOOBE",
    ]:
        data["oobe"][key] = form.get(f"oobe_{key}", "false")
    data["oobe"]["NetworkLocation"] = form.get("oobe_NetworkLocation", "Work")
    data["oobe"]["ProtectYourPC"] = form.get("oobe_ProtectYourPC", "3")

    fl_cls = form.getlist("fl_cmd_commandline[]")
    fl_descs = form.getlist("fl_cmd_desc[]")
    data["firstlogon_commands"] = []
    for i in range(len(fl_cls)):
        if fl_cls[i].strip():
            data["firstlogon_commands"].append({
                "commandline": fl_cls[i],
                "description": fl_descs[i] if i < len(fl_descs) else "",
            })

    accounts = []
    i = 0
    while form.get(f"account_name_{i}"):
        accounts.append({
            "name": form.get(f"account_name_{i}", ""),
            "password": form.get(f"account_password_{i}", ""),
            "plain_text": form.get(f"account_plaintext_{i}", "true"),
            "group": form.get(f"account_group_{i}", "Administrators"),
            "display_name": form.get(f"account_display_{i}", ""),
        })
        i += 1
    data["user_accounts"] = accounts

    data["autologon"] = {
        "enabled": form.get("autologon_enabled", ""),
        "username": form.get("autologon_username", ""),
        "password": form.get("autologon_password", ""),
        "plain_text": form.get("autologon_plaintext", "true"),
        "logon_count": form.get("autologon_logoncount", ""),
    }

    data["intl"] = {
        "input_locale": form.get("intl_input_locale", ""),
        "system_locale": form.get("intl_system_locale", ""),
        "ui_language": form.get("intl_ui_language", ""),
        "user_locale": form.get("intl_user_locale", ""),
    }

    data["oobe_timezone"] = form.get("oobe_timezone", "")

    return data
