"""Evidence-based structured metadata projection for Config object payloads."""

from __future__ import annotations

from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any

from .child_records import collect_evidence, declared_child_records
from .config_object import find_identity
from .payload import GUID_RE, parse_payload_file, root_signature, scalar
from .xml_metadata import XmlMetadataItem, extract_xml_metadata_items, group_xml_items


CATEGORY_FIELDS = {
    "Attribute": "attributes",
    "TabularSection": "tabular_sections",
    "Dimension": "dimensions",
    "Resource": "resources",
    "Form": "forms",
    "Template": "templates",
    "Command": "commands",
    "AddressingAttribute": "addressing_attributes",
    "AccountingFlag": "accounting_flags",
    "Column": "columns",
    "EnumValue": "enum_values",
    "IntegrationServiceChannel": "integration_service_channels",
    "Operation": "operations",
    "URLTemplate": "url_templates",
}


@dataclass(frozen=True)
class MetadataItemEvidence:
    category: str
    name: str
    synonym: str
    uuid: str | None
    value_type: dict[str, Any] | None
    parent_category: str | None
    parent_name: str | None
    parent_uuid: str | None
    section_path: str
    record_path: str | None
    record_index: int | None
    evidence: dict[str, bool]

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


def _children(node: Any) -> list[Any]:
    if isinstance(node, dict) and node.get("type") in {"list", "sequence"}:
        return node.get("items") or []
    return []


def get_by_path(tree: Any, path: str) -> Any | None:
    node = tree
    if path == "":
        return node
    for part in path.split("."):
        if not isinstance(node, dict) or node.get("type") not in {"list", "sequence"}:
            return None
        items = node.get("items") or []
        index = int(part)
        if index < 0 or index >= len(items):
            return None
        node = items[index]
    return node


def section_rules(summary: dict[str, Any], kind: str, min_support_ratio: float) -> list[dict[str, Any]]:
    rules = []
    for section in summary.get("sections") or []:
        if section.get("kind") != kind:
            continue
        category = section.get("candidate_semantic")
        if not category or category == kind:
            continue
        support_ratio = float(section.get("candidate_support_ratio") or 0)
        if support_ratio < min_support_ratio:
            continue
        rules.append(
            {
                "path": section["path"],
                "category": category,
                "support_ratio": support_ratio,
                "sample_count": section.get("sample_count"),
            }
        )
    return rules


def item_evidence(
    item: XmlMetadataItem,
    evidence: dict[str, set[str]],
    section_path: str,
    *,
    record_path: str | None = None,
    record_index: int | None = None,
) -> MetadataItemEvidence | None:
    strings = evidence["strings"]
    guids = evidence["guids"]
    hits = {
        "name": bool(item.name and item.name in strings),
        "synonym": bool(item.synonym and item.synonym in strings),
        "uuid": bool(item.uuid and item.uuid.lower() in guids),
    }
    if not any(hits.values()):
        return None
    return MetadataItemEvidence(
        category=item.category,
        name=item.name,
        synonym=item.synonym,
        uuid=item.uuid.lower() if item.uuid else None,
        value_type=item.value_type,
        parent_category=item.parent_category,
        parent_name=item.parent_name,
        parent_uuid=item.parent_uuid.lower() if item.parent_uuid else None,
        section_path=section_path,
        record_path=record_path,
        record_index=record_index,
        evidence=hits,
    )


def best_item_record_match(item: XmlMetadataItem, records: list[Any], section_path: str) -> MetadataItemEvidence | None:
    best: MetadataItemEvidence | None = None
    best_score = -1
    for record in records:
        match = item_evidence(
            item,
            record.evidence,
            section_path,
            record_path=record.path,
            record_index=record.index,
        )
        if not match:
            continue
        score = int(match.evidence["uuid"]) * 4 + int(match.evidence["name"]) * 2 + int(match.evidence["synonym"])
        if score > best_score:
            best = match
            best_score = score
    return best


def items_for_parent(grouped: dict[str, list[XmlMetadataItem]], category: str, parent_uuid: str | None) -> list[XmlMetadataItem]:
    return [
        item
        for item in grouped.get(category, [])
        if (item.parent_uuid or "").lower() == (parent_uuid or "").lower()
    ]


def declared_record_containers(node: Any, path: str, *, max_depth: int = 3, include_root: bool = True) -> list[list[Any]]:
    result = []

    def walk(value: Any, current_path: str, depth: int) -> None:
        records = declared_child_records(value, current_path)
        if records and (include_root or depth > 0):
            result.append(records)
        if depth >= max_depth:
            return
        for index, child in enumerate(_children(value)):
            walk(child, f"{current_path}.{index}", depth + 1)

    walk(node, path, 0)
    return result


def nested_tabular_attributes(
    tree: Any,
    tabular_section_item: dict[str, Any],
    grouped: dict[str, list[XmlMetadataItem]],
) -> list[dict[str, Any]]:
    parent_uuid = tabular_section_item.get("uuid")
    record_path = tabular_section_item.get("record_path")
    if not parent_uuid or not record_path:
        return []
    node = get_by_path(tree, record_path)
    if node is None:
        return []
    xml_items = items_for_parent(grouped, "Attribute", parent_uuid)
    if not xml_items:
        return []
    containers = declared_record_containers(node, record_path, include_root=False)
    all_records = [record for records in containers for record in records]
    result = []
    for item in xml_items:
        match = best_item_record_match(item, all_records, record_path)
        if match:
            data = match.to_dict()
            data["tabular_section_name"] = tabular_section_item.get("name")
            data["tabular_section_uuid"] = parent_uuid
            result.append(data)
    result.sort(key=lambda item: (item["tabular_section_name"] or "", item["name"], item.get("uuid") or ""))
    return result


def parse_structured_metadata(
    config_file: Path,
    xml_file: Path,
    kind: str,
    category_summary: dict[str, Any],
    *,
    min_support_ratio: float = 1.0,
) -> dict[str, Any]:
    parsed = parse_payload_file(config_file)
    tree = parsed.get("tree")
    identity = find_identity(tree)
    xml_items = extract_xml_metadata_items(xml_file)
    grouped = group_xml_items(xml_items)
    rules = section_rules(category_summary, kind, min_support_ratio)

    result: dict[str, Any] = {
        "schema": "onec_structured_metadata_projection.v1",
        "kind": kind,
        "config_file": str(config_file),
        "xml_file": str(xml_file),
        "root": root_signature(tree),
        "identity": identity.to_dict() if identity else None,
        "min_support_ratio": min_support_ratio,
        "rules": rules,
        "attributes": [],
        "tabular_sections": [],
        "tabular_section_attributes": [],
        "dimensions": [],
        "resources": [],
        "forms": [],
        "templates": [],
        "commands": [],
        "addressing_attributes": [],
        "accounting_flags": [],
        "columns": [],
        "enum_values": [],
        "integration_service_channels": [],
        "operations": [],
        "url_templates": [],
        "unmapped_rules": [],
        "record_boundary_rules": [],
    }

    for rule in rules:
        category = rule["category"]
        field = CATEGORY_FIELDS.get(category)
        node = get_by_path(tree, rule["path"]) if tree else None
        if not field or node is None:
            result["unmapped_rules"].append(rule)
            continue
        records = declared_child_records(node, rule["path"])
        if records:
            result["record_boundary_rules"].append(
                {
                    "path": rule["path"],
                    "category": category,
                    "declared_record_count": len(records),
                    "record_paths_sample": [record.path for record in records[:10]],
                }
            )
        section_evidence = collect_evidence(node)
        matched = []
        for item in items_for_parent(grouped, category, identity.guid if identity else None):
            item_match = best_item_record_match(item, records, rule["path"]) if records else None
            if not item_match:
                item_match = item_evidence(item, section_evidence, rule["path"])
            if item_match:
                matched.append(item_match.to_dict())
        matched.sort(key=lambda item: (item["name"], item.get("uuid") or ""))
        result[field].extend(matched)
        if category == "TabularSection":
            for tabular_section in matched:
                result["tabular_section_attributes"].extend(nested_tabular_attributes(tree, tabular_section, grouped))

    result["counts"] = {
        field: len(result[field])
        for field in sorted({*set(CATEGORY_FIELDS.values()), "tabular_section_attributes"})
    }
    return result
