"""Small XML metadata extractor used as validation oracle for SQL payloads."""

from __future__ import annotations

import xml.etree.ElementTree as ET
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any


def local_name(tag: str) -> str:
    return tag.rsplit("}", 1)[-1] if "}" in tag else tag


@dataclass(frozen=True)
class XmlMetadataItem:
    category: str
    name: str
    synonym: str
    uuid: str | None
    value_type: dict[str, Any] | None = None
    parent_category: str | None = None
    parent_name: str | None = None
    parent_uuid: str | None = None

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


def direct_child(parent: ET.Element, name: str) -> ET.Element | None:
    for child in list(parent):
        if local_name(child.tag) == name:
            return child
    return None


def child_text(parent: ET.Element, name: str) -> str:
    child = direct_child(parent, name)
    return (child.text or "").strip() if child is not None else ""


def synonym_text(properties: ET.Element | None) -> str:
    if properties is None:
        return ""
    synonym = direct_child(properties, "Synonym")
    if synonym is None:
        return ""
    for node in synonym.iter():
        if local_name(node.tag) == "content" and node.text:
            return node.text.strip()
    return ""


def value_type(properties: ET.Element | None) -> dict[str, Any] | None:
    if properties is None:
        return None
    type_node = direct_child(properties, "Type")
    if type_node is None:
        return None
    types = []
    qualifiers: dict[str, Any] = {}
    for node in type_node.iter():
        name = local_name(node.tag)
        text = (node.text or "").strip()
        if name == "Type" and node is not type_node and text:
            types.append(text)
        elif name in {"Length", "AllowedLength", "Digits", "FractionDigits", "AllowedSign", "DateFractions"} and text:
            qualifiers[name] = text
    if not types and not qualifiers:
        return None
    return {
        "types": types,
        "qualifiers": qualifiers,
        "is_composite": len(types) > 1,
    }


def item_from_properties(
    category: str,
    node: ET.Element,
    properties: ET.Element | None,
    *,
    parent: XmlMetadataItem | None = None,
) -> XmlMetadataItem:
    return XmlMetadataItem(
        category=category,
        name=child_text(properties, "Name") if properties is not None else node.attrib.get("name", ""),
        synonym=synonym_text(properties),
        uuid=node.attrib.get("uuid"),
        value_type=value_type(properties),
        parent_category=parent.category if parent else None,
        parent_name=parent.name if parent else None,
        parent_uuid=parent.uuid.lower() if parent and parent.uuid else None,
    )


def extract_xml_metadata_items(path: Path) -> list[XmlMetadataItem]:
    root = ET.parse(path).getroot()
    metadata_node = next(iter(list(root)), None)
    if metadata_node is None:
        return []

    result: list[XmlMetadataItem] = []
    properties = direct_child(metadata_node, "Properties")
    root_item = item_from_properties(local_name(metadata_node.tag), metadata_node, properties)
    result.append(root_item)

    internal = direct_child(metadata_node, "InternalInfo")
    if internal is not None:
        for generated in internal.iter():
            if local_name(generated.tag) == "GeneratedType":
                result.append(
                    XmlMetadataItem(
                        category="GeneratedType",
                        name=generated.attrib.get("name", ""),
                        synonym=generated.attrib.get("category", ""),
                        uuid=None,
                    )
                )

    if properties is not None:
        standard = direct_child(properties, "StandardAttributes")
        if standard is not None:
            for node in list(standard):
                if local_name(node.tag) == "StandardAttribute":
                    result.append(
                        XmlMetadataItem(
                            category="StandardAttribute",
                            name=node.attrib.get("name", ""),
                            synonym=synonym_text(node),
                            uuid=None,
                        )
                    )

    child_objects = direct_child(metadata_node, "ChildObjects")
    if child_objects is not None:
        for node in list(child_objects):
            category = local_name(node.tag)
            props = direct_child(node, "Properties")
            child_item = item_from_properties(category, node, props, parent=root_item)
            result.append(child_item)
            append_nested_child_objects(result, node, child_item)
    return result


def append_nested_child_objects(result: list[XmlMetadataItem], parent_node: ET.Element, parent_item: XmlMetadataItem) -> None:
    child_objects = direct_child(parent_node, "ChildObjects")
    if child_objects is None:
        return
    for node in list(child_objects):
        category = local_name(node.tag)
        props = direct_child(node, "Properties")
        child_item = item_from_properties(category, node, props, parent=parent_item)
        result.append(child_item)
        append_nested_child_objects(result, node, child_item)


def group_xml_items(items: list[XmlMetadataItem]) -> dict[str, list[XmlMetadataItem]]:
    grouped: dict[str, list[XmlMetadataItem]] = {}
    for item in items:
        grouped.setdefault(item.category, []).append(item)
    return grouped
