"""Decoder for exported 1C ``ParentConfigurations.bin`` support rules.

The SQL representation of these data is platform-private and may differ from
the exported representation.  Callers must therefore pass bytes from a
positively identified source; this module deliberately does not discover a
source or infer that missing data means "not on support".
"""

from __future__ import annotations

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

from .payload import parse_brace_text, payload_to_text, scalar


GUID_RE = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.I)
OBJECT_RULES = {
    0: "not_editable",
    1: "editable_support_preserved",
    2: "not_supported",
}


@dataclass(frozen=True)
class SupportRule:
    object_guid: str
    rule_code: int
    rule: str

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


@dataclass(frozen=True)
class SupplierSupport:
    configuration_guid: str
    general_mode_code: int
    general_mode: str
    version: str
    producer: str
    name: str
    declared_object_count: int
    rules: tuple[SupportRule, ...]

    def to_dict(self) -> dict[str, Any]:
        result = asdict(self)
        result["rules"] = [rule.to_dict() for rule in self.rules]
        return result


def _root_items(data: bytes, source: str) -> tuple[list[dict[str, Any]], dict[str, Any]]:
    decoded = payload_to_text(data)
    text = decoded.get("text")
    if text is None:
        raise ValueError(f"{source}: cannot decode support rules text")
    root = parse_brace_text(text)
    if not (isinstance(root, dict) and root.get("type") == "list"):
        raise ValueError(f"{source}: expected root list")
    return list(root.get("items") or []), decoded


def _integer(items: list[dict[str, Any]], index: int, source: str, field: str) -> int:
    try:
        return int(scalar(items[index]))
    except (IndexError, TypeError, ValueError) as exc:
        raise ValueError(f"{source}: invalid {field} at item {index}") from exc


def parse_parent_configurations_bytes(
    data: bytes,
    *,
    source: str = "ParentConfigurations.bin",
) -> dict[str, Any]:
    """Decode a positively identified exported support-rules payload."""

    items, decoded = _root_items(data, source)
    if len(items) < 3:
        raise ValueError(f"{source}: support rules header is incomplete")
    format_marker = _integer(items, 0, source, "format marker")
    if format_marker != 6:
        raise ValueError(f"{source}: expected format marker 6, got {format_marker}")
    supplier_count = _integer(items, 2, source, "supplier count")
    if supplier_count < 0:
        raise ValueError(f"{source}: supplier count must not be negative")

    suppliers: list[SupplierSupport] = []
    position = 3
    for supplier_index in range(supplier_count):
        if position + 6 >= len(items):
            raise ValueError(f"{source}: supplier {supplier_index} header is incomplete")
        configuration_guid = scalar(items[position]).lower()
        if not GUID_RE.fullmatch(configuration_guid):
            raise ValueError(f"{source}: supplier {supplier_index} configuration GUID is invalid")
        general_code = _integer(items, position + 1, source, "general support mode")
        object_count = _integer(items, position + 6, source, "object count")
        if object_count < 0:
            raise ValueError(f"{source}: supplier {supplier_index} object count must not be negative")
        object_position = position + 7
        rules: list[SupportRule] = []
        for object_index in range(object_count):
            current = object_position + object_index * 4
            if current + 3 >= len(items):
                raise ValueError(f"{source}: supplier {supplier_index} object {object_index} is incomplete")
            rule_code = _integer(items, current, source, "object support rule")
            object_guid = scalar(items[current + 2]).lower()
            if rule_code not in OBJECT_RULES:
                raise ValueError(f"{source}: unsupported object rule code {rule_code}")
            if not GUID_RE.fullmatch(object_guid):
                raise ValueError(f"{source}: supplier {supplier_index} object {object_index} GUID is invalid")
            effective_code = 0 if general_code != 0 else rule_code
            rules.append(SupportRule(object_guid, effective_code, OBJECT_RULES[effective_code]))
        suppliers.append(
            SupplierSupport(
                configuration_guid=configuration_guid,
                general_mode_code=general_code,
                general_mode="editable" if general_code == 0 else "locked",
                version=scalar(items[position + 3]),
                producer=scalar(items[position + 4]),
                name=scalar(items[position + 5]),
                declared_object_count=object_count,
                rules=tuple(rules),
            )
        )
        position = object_position + object_count * 4 + 2

    return {
        "source": source,
        "compression": decoded["compression"],
        "encoding": decoded["encoding"],
        "format_marker": format_marker,
        "supplier_count": supplier_count,
        "suppliers": suppliers,
    }


def parse_parent_configurations_file(path: Path) -> dict[str, Any]:
    return parse_parent_configurations_bytes(path.read_bytes(), source=path.name)
