from __future__ import annotations

from pathlib import Path
from typing import Any


class _SimpleElement:
    def __init__(self, *, id: str, type: str, name: str | None, properties: dict[str, Any]):
        self.id = id
        self.type = type
        self.name = name
        self.properties = properties


class _SimpleRelationship:
    def __init__(
        self,
        *,
        id: str,
        type: str,
        name: str | None,
        source: str,
        target: str,
        properties: dict[str, Any],
    ):
        self.id = id
        self.type = type
        self.name = name
        self.source = source
        self.target = target
        self.properties = properties


class _SimpleModel:
    def __init__(self) -> None:
        self.elements: list[_SimpleElement] = []
        self.relationships: list[_SimpleRelationship] = []


def _strip_archimate_type(raw: str | None) -> str:
    if not raw:
        return "Unknown"
    # e.g. "archimate:BusinessProcess" -> "BusinessProcess"
    if ":" in raw:
        return raw.split(":", 1)[1]
    return raw


def _load_model_via_xml(path: Path) -> Any:
    """
    Fallback loader that parses ArchiMate XML directly.

    This is designed to be tolerant to unknown element types (e.g. BusinessInteraction)
    that may cause pyArchimate to throw KeyError during `Model.read`.
    """
    import xml.etree.ElementTree as ET

    # Archi exports often use a prefixed namespace for the root element
    # (e.g. <archimate:model ...>) but leave child tags unprefixed
    # (<folder>, <element>). So we must accept both qualified and unqualified tags.
    ns = {
        "archimate": "http://www.archimatetool.com/archimate",
        "xsi": "http://www.w3.org/2001/XMLSchema-instance",
    }

    tree = ET.parse(str(path))
    root = tree.getroot()

    model = _SimpleModel()

    def _is_folder_tag(tag: str) -> bool:
        return tag in ("folder", f"{{{ns['archimate']}}}folder")

    def _is_element_tag(tag: str) -> bool:
        return tag in ("element", f"{{{ns['archimate']}}}element")

    def walk_folder(node: ET.Element, folder_path: list[str]) -> None:
        tag = node.tag
        # Folder nodes are <folder ...> (often unqualified) or namespaced.
        if _is_folder_tag(tag):
            name = node.get("name") or ""
            next_path = folder_path + ([name] if name else [])
        else:
            next_path = folder_path

        for child in list(node):
            if _is_folder_tag(child.tag):
                walk_folder(child, next_path)
                continue

            if not _is_element_tag(child.tag):
                # Views and other structures are ignored for MVP.
                walk_folder(child, next_path) if list(child) else None
                continue

            elem_id = (child.get("id") or "").strip()
            elem_name = (child.get("name") or "").strip() or None
            elem_type = _strip_archimate_type(child.get(f"{{{ns['xsi']}}}type"))

            source = (child.get("source") or "").strip()
            target = (child.get("target") or "").strip()

            props: dict[str, Any] = {}
            if next_path:
                props["folder_path"] = "/".join(next_path)
            folder_type = child.get("type")
            if folder_type:
                props["folder_type"] = folder_type

            # Copy unknown attributes for traceability.
            for k, v in child.attrib.items():
                if k in ("id", "name", "source", "target") or k.endswith("}type"):
                    continue
                props.setdefault("attrs", {})[k] = v

            if source and target:
                model.relationships.append(
                    _SimpleRelationship(
                        id=elem_id or f"{source}->{target}:{elem_type}",
                        type=elem_type,
                        name=elem_name,
                        source=source,
                        target=target,
                        properties=props,
                    )
                )
            else:
                model.elements.append(
                    _SimpleElement(
                        id=elem_id,
                        type=elem_type,
                        name=elem_name,
                        properties=props,
                    )
                )

    walk_folder(root, [])
    return model


def load_model(path: str | Path) -> Any:
    """
    Load an ArchiMate model file using pyArchimate.

    This is a thin wrapper so that the rest of the module does not depend
    directly on the concrete API surface of pyArchimate. The caller receives
    the underlying model object and passes it to `mapper.map_to_graph`.

    Parameters
    ----------
    path:
        Path to an ArchiMate model file (.archimate / .xml) that follows
        the Open Group ArchiMate Model Exchange File Format.
    """
    try:
        # pyArchimate exposes `Model` and provides `Model.read(file_path)`.
        from pyArchimate import Model  # type: ignore
    except ImportError as exc:  # pragma: no cover - import error path
        raise RuntimeError(
            "pyArchimate is required to load ArchiMate models. "
            "Install it with `pip install pyArchimate`."
        ) from exc

    model_path = Path(path)
    if not model_path.exists():
        raise FileNotFoundError(f"ArchiMate model file not found: {model_path}")

    model = Model()
    try:
        model.read(str(model_path))
        return model
    except KeyError:
        # pyArchimate can fail on unknown concept types. Fall back to tolerant XML parsing.
        return _load_model_via_xml(model_path)

