"""Deterministic norm text builder for embedding pipelines."""
from __future__ import annotations

import hashlib
import re
from typing import Any, Dict, List, Mapping, Tuple

from normbase.models.norm import Norm

BUILDER_VERSION = "norm_nl_v1"
_SPACE_RE = re.compile(r"\s+")


def _normalize_text(value: Any) -> str:
    text = str(value or "").strip()
    if not text:
        return ""
    return _SPACE_RE.sub(" ", text)


def _normalize_string_list(values: Any) -> List[str]:
    if not isinstance(values, list):
        return []
    cleaned = {_normalize_text(item) for item in values}
    return sorted(item for item in cleaned if item)


def _as_mapping(value: Any) -> Mapping[str, Any]:
    if isinstance(value, Mapping):
        return value
    return {}


def _canonical_section(zone: Any) -> Mapping[str, Any]:
    canonical = _as_mapping(zone).get("canonical")
    return _as_mapping(canonical)


def _select_nl_text(zone: Any, preferred_lang: str | None = None) -> tuple[str | None, str]:
    nl = _as_mapping(_as_mapping(zone).get("nl"))
    texts = _as_mapping(nl.get("texts"))
    preferred = _normalize_text(preferred_lang)
    if preferred:
        preferred_value = _normalize_text(texts.get(preferred))
        if preferred_value:
            return preferred, preferred_value
    default_lang = _normalize_text(nl.get("default_lang"))
    if default_lang:
        return default_lang, _normalize_text(texts.get(default_lang))
    return None, ""


def _add_line(lines: List[str], key: str, value: Any) -> None:
    text = _normalize_text(value)
    if text:
        lines.append(f"{key}: {text}")


def _add_list_line(lines: List[str], key: str, values: Any) -> None:
    normalized = _normalize_string_list(values)
    if normalized:
        lines.append(f"{key}: {', '.join(normalized)}")


def _build_tags_line(norm: Norm) -> str:
    grouped = build_norm_tag_groups(norm)
    if not grouped:
        return ""
    segments: List[str] = []
    for category in sorted(grouped):
        tags = sorted(grouped[category])
        segments.append(f"{category}={'|'.join(tags)}")
    return "; ".join(segments)


def build_norm_tag_groups(norm: Norm) -> Dict[str, List[str]]:
    """Build deterministic category -> tags map from norm tags."""
    grouped: Dict[str, set[str]] = {}
    for tag_row in getattr(norm, "tags", []) or []:
        category = _normalize_text(getattr(tag_row, "tag_category", ""))
        tag = _normalize_text(getattr(tag_row, "tag", ""))
        if not category or not tag:
            continue
        grouped.setdefault(category, set()).add(tag)
    return {category: sorted(values) for category, values in sorted(grouped.items())}


def build_norm_subject_metadata(norm: Norm) -> Tuple[List[str], List[str]]:
    """Return deterministic subject roles/agents arrays from norm subject canonical."""
    subject = _as_mapping(getattr(norm, "subject", {}))
    canonical = _canonical_section(subject)
    roles = _normalize_string_list(canonical.get("roles"))
    agents = _normalize_string_list(canonical.get("agents"))
    return roles, agents


def build_norm_nl_text(norm: Norm, preferred_lang: str | None = None) -> str:
    """Build deterministic text used for norm embedding."""
    lines: List[str] = []

    _add_line(lines, "Norm-Id", getattr(norm, "id", ""))
    _add_line(lines, "Title", getattr(norm, "title", ""))
    _add_line(lines, "Norm-Type", getattr(norm, "norm_type", ""))
    _add_line(lines, "Authority-Level", getattr(norm, "authority_level", ""))
    _add_line(lines, "Formality-Level", getattr(norm, "formality_level", ""))
    _add_line(lines, "Status", getattr(norm, "status", ""))

    subject = _as_mapping(getattr(norm, "subject", {}))
    subject_canonical = _canonical_section(subject)
    _add_list_line(lines, "Subject-Roles", subject_canonical.get("roles"))
    _add_list_line(
        lines,
        "Subject-Agents",
        [str(item) for item in _normalize_string_list(subject_canonical.get("agents"))],
    )
    _add_list_line(lines, "Subject-Capabilities", subject_canonical.get("capabilities"))
    subject_lang, subject_text = _select_nl_text(subject, preferred_lang)
    if subject_lang and subject_text:
        lines.append(f"Subject-NL({subject_lang}): {subject_text}")

    context = _as_mapping(getattr(norm, "context", {}))
    context_canonical = _canonical_section(context)
    _add_list_line(lines, "Context-Regions", context_canonical.get("regions"))
    _add_list_line(lines, "Context-Domains", context_canonical.get("domains"))
    _add_list_line(lines, "Context-Scenarios", context_canonical.get("scenarios"))
    context_lang, context_text = _select_nl_text(context, preferred_lang)
    if context_lang and context_text:
        lines.append(f"Context-NL({context_lang}): {context_text}")

    condition = _as_mapping(getattr(norm, "condition", {}))
    condition_canonical = _canonical_section(condition)
    _add_line(lines, "Condition-Trigger-Type", condition_canonical.get("trigger_type"))
    condition_lang, condition_text = _select_nl_text(condition, preferred_lang)
    if condition_lang and condition_text:
        lines.append(f"Condition-NL({condition_lang}): {condition_text}")

    action = _as_mapping(getattr(norm, "action", {}))
    action_canonical = _canonical_section(action)
    _add_line(lines, "Action-Modality", action_canonical.get("modality"))
    _add_list_line(lines, "Action-Operations", action_canonical.get("operations"))
    _add_list_line(lines, "Action-Targets", action_canonical.get("targets"))
    _add_list_line(lines, "Action-Tools", action_canonical.get("tools"))
    _add_list_line(lines, "Action-Skills", action_canonical.get("skills"))
    _add_line(lines, "Action-Time-Limit", action_canonical.get("time_limit"))
    approval_required = action_canonical.get("approval_required")
    if isinstance(approval_required, bool):
        lines.append(f"Action-Approval-Required: {'true' if approval_required else 'false'}")
    action_lang, action_text = _select_nl_text(action, preferred_lang)
    if action_lang and action_text:
        lines.append(f"Action-NL({action_lang}): {action_text}")

    consequence = _as_mapping(getattr(norm, "consequence", {}))
    consequence_canonical = _canonical_section(consequence)
    on_violation = _as_mapping(consequence_canonical.get("on_violation"))
    on_compliance = _as_mapping(consequence_canonical.get("on_compliance"))
    _add_line(lines, "Consequence-Violation-Severity", on_violation.get("severity"))
    _add_list_line(lines, "Consequence-Violation-Responses", on_violation.get("responses"))
    _add_list_line(lines, "Consequence-Compliance-Rewards", on_compliance.get("rewards"))
    consequence_lang, consequence_text = _select_nl_text(consequence, preferred_lang)
    if consequence_lang and consequence_text:
        lines.append(f"Consequence-NL({consequence_lang}): {consequence_text}")

    _add_line(lines, "Source", getattr(norm, "source", ""))
    tags_text = _build_tags_line(norm)
    _add_line(lines, "Tags", tags_text)
    lines.append(f"Builder-Version: {BUILDER_VERSION}")
    return "\n".join(lines)


def build_norm_text_hash(text: str) -> str:
    """Compute stable sha256 hash for generated embedding text."""
    payload = str(text or "")
    return hashlib.sha256(payload.encode("utf-8")).hexdigest()
