#!/usr/bin/env python3
"""干预数据脚本的公共能力。

这里尽量只做确定性处理：
- 读取历史干预 TSV。
- 归纳设备枚举和 code 到下发 domain 的多数映射。
- 复用 label-master 校验 target 语法。
- 校验候选干预条目是否和历史数据重复或冲突。
"""

from __future__ import annotations

import csv
import importlib.util
import json
import re
import sys
from collections import Counter, defaultdict
from dataclasses import dataclass
from pathlib import Path
from typing import Any


SCRIPT_DIR = Path(__file__).resolve().parent
SKILL_ROOT = SCRIPT_DIR.parent
REPO_ROOT = SKILL_ROOT.parent.parent
DEFAULT_EXACT_SOURCE = REPO_ROOT / "干预skill" / "20260134"
DEFAULT_REGEX_SOURCE = REPO_ROOT / "干预skill" / "20260207"
LABEL_VALIDATOR_PATH = REPO_ROOT / "skills" / "label-master" / "scripts" / "validate_label_output.py"
CONFIG_MAPPING_PATH = SKILL_ROOT / "knowledge" / "type_match_agent.json"
AGENT_TARGET_RE = re.compile(r'^Agent\(\s*tag\s*=\s*["\'](.+?)["\']\s*\)$')


@dataclass(frozen=True)
class ExactRecord:
    device: str
    query: str
    target: str
    dispatch_domain: str
    line_no: int


@dataclass(frozen=True)
class RegexRecord:
    device: str
    pattern: str
    target: str
    line_no: int


def normalize_device(device: str) -> str:
    """归一化历史数据里的设备写法，但保留常见大小写。"""

    value = device.strip()
    if value.startswith("#"):
        value = value[1:].strip()
    aliases = {
        "micar": "miCar",
        "miai": "miai",
        "tVV2": "tvV2",
        "tvv2": "tvV2",
        "unify": "unify",
    }
    return aliases.get(value, value)


def normalize_target(target: str) -> str:
    return target.replace("“", '"').replace("”", '"').replace("‘", "'").replace("’", "'").strip()


def read_tsv_rows(path: Path) -> list[list[str]]:
    with path.open("r", encoding="utf-8-sig", newline="") as handle:
        return list(csv.reader(handle, delimiter="\t"))


def load_exact_records(path: Path = DEFAULT_EXACT_SOURCE) -> list[ExactRecord]:
    rows = read_tsv_rows(path)
    records: list[ExactRecord] = []
    for index, row in enumerate(rows, start=1):
        if index == 1 and row[:4] == ["#设备名", "query", "code", "下发domain"]:
            continue
        if not row or all(not cell.strip() for cell in row):
            continue
        padded = [*row, "", "", "", ""]
        records.append(
            ExactRecord(
                device=normalize_device(padded[0]),
                query=padded[1].strip(),
                target=normalize_target(padded[2]),
                dispatch_domain=padded[3].strip(),
                line_no=index,
            )
        )
    return records


def load_regex_records(path: Path = DEFAULT_REGEX_SOURCE) -> list[RegexRecord]:
    rows = read_tsv_rows(path)
    records: list[RegexRecord] = []
    for index, row in enumerate(rows, start=1):
        if not row or all(not cell.strip() for cell in row):
            continue
        padded = [*row, "", ""]
        records.append(
            RegexRecord(
                device=normalize_device(padded[0]),
                pattern=padded[1].strip(),
                target=normalize_target(padded[2]),
                line_no=index,
            )
        )
    return records


def build_source_summary(
    exact_path: Path = DEFAULT_EXACT_SOURCE,
    regex_path: Path = DEFAULT_REGEX_SOURCE,
) -> dict[str, Any]:
    exact_records = load_exact_records(exact_path) if exact_path.exists() else []
    regex_records = load_regex_records(regex_path) if regex_path.exists() else []

    exact_devices = Counter(record.device for record in exact_records)
    regex_devices = Counter(record.device for record in regex_records)
    devices = sorted(set(exact_devices) | set(regex_devices))

    code_domain_counts: dict[str, Counter[str]] = defaultdict(Counter)
    for record in exact_records:
        if record.target and record.dispatch_domain:
            code_domain_counts[record.target][record.dispatch_domain] += 1

    code_domain_mapping: dict[str, dict[str, Any]] = {}
    for target, counts in code_domain_counts.items():
        domain, count = counts.most_common(1)[0]
        total = sum(counts.values())
        code_domain_mapping[target] = {
            "dispatch_domain": domain,
            "count": count,
            "total": total,
            "confidence": round(count / total, 4) if total else 0,
            "alternatives": [
                {"dispatch_domain": item_domain, "count": item_count}
                for item_domain, item_count in counts.most_common(5)
            ],
        }

    config_mapping = load_type_match_agent_mapping()

    return {
        "source_files": {
            "exact": str(exact_path),
            "regex": str(regex_path),
        },
        "counts": {
            "exact_records": len(exact_records),
            "regex_records": len(regex_records),
        },
        "devices": devices,
        "device_counts": {
            "exact": dict(exact_devices.most_common()),
            "regex": dict(regex_devices.most_common()),
        },
        "code_domain_mapping": code_domain_mapping,
        "configured_code_domain_mapping": config_mapping,
        "historical_targets": sorted({record.target for record in exact_records + regex_records if record.target}),
        "top_exact_targets": dict(Counter(record.target for record in exact_records).most_common(50)),
        "top_regex_targets": dict(Counter(record.target for record in regex_records).most_common(50)),
    }


def historical_targets(summary: dict[str, Any]) -> set[str]:
    targets = set(summary.get("top_exact_targets", {})) | set(summary.get("top_regex_targets", {}))
    targets.update(summary.get("code_domain_mapping", {}))
    targets.update(summary.get("historical_targets", []))
    return {normalize_target(target) for target in targets if target}


def infer_dispatch_domain(target: str, summary: dict[str, Any]) -> dict[str, Any] | None:
    normalized = normalize_target(target)
    historical = summary.get("code_domain_mapping", {}).get(normalized)
    if historical:
        return {**historical, "source": "historical"}
    configured = infer_configured_dispatch_domain(normalized, "unify", summary)
    if configured:
        return {
            "dispatch_domain": configured["dispatch_domain"],
            "count": 0,
            "total": 0,
            "confidence": None,
            "alternatives": [],
            "source": "type_match_agent",
            "matched_key": configured["matched_key"],
        }
    return None


def infer_dispatch_domain_for_device(target: str, device: str, summary: dict[str, Any]) -> dict[str, Any] | None:
    """按设备推断推荐下发 domain。

    优先级：
    1. `标签#设备` 这种明确配置。
    2. 历史精确干预表里的全局多数映射。
    3. `标签` 通用配置。
    """

    normalized = normalize_target(target)
    configured = infer_configured_dispatch_domain(normalized, device, summary)
    if configured and "#" in configured["matched_key"]:
        return {
            "dispatch_domain": configured["dispatch_domain"],
            "count": 0,
            "total": 0,
            "confidence": None,
            "alternatives": [],
            "source": "type_match_agent",
            "matched_key": configured["matched_key"],
        }

    historical = summary.get("code_domain_mapping", {}).get(normalized)
    if historical:
        return {**historical, "source": "historical"}

    if configured:
        return {
            "dispatch_domain": configured["dispatch_domain"],
            "count": 0,
            "total": 0,
            "confidence": None,
            "alternatives": [],
            "source": "type_match_agent",
            "matched_key": configured["matched_key"],
        }
    return None


def load_type_match_agent_mapping(path: Path = CONFIG_MAPPING_PATH) -> dict[str, str]:
    if not path.exists():
        return {}
    data = json.loads(path.read_text(encoding="utf-8"))
    return {str(key): str(value) for key, value in data.items()}


def extract_agent_tag(target: str) -> str | None:
    match = AGENT_TARGET_RE.match(normalize_target(target))
    if not match:
        return None
    return match.group(1)


def infer_configured_dispatch_domain(target: str, device: str, summary: dict[str, Any]) -> dict[str, str] | None:
    tag = extract_agent_tag(target)
    if not tag:
        return None
    mapping = summary.get("configured_code_domain_mapping", {})
    normalized_device = normalize_device(device)
    for key in (f"{tag}#{normalized_device}", tag):
        if key in mapping:
            return {"matched_key": key, "dispatch_domain": mapping[key]}
    return None


def split_domain(domain: str) -> list[str]:
    return [part for part in domain.split("|") if part]


def domain_compatible(actual: str, expected: str) -> bool:
    """判断实际下发 domain 是否和配置基线兼容。

    下发表里有些是宽泛入口，例如 `音乐 -> contentCopilot`；
    历史干预里常见更细路径，例如 `contentCopilot|music`。
    只要二者一方的管道 token 是另一方的子集，就认为兼容。
    """

    actual_parts = set(split_domain(actual))
    expected_parts = set(split_domain(expected))
    if not actual_parts or not expected_parts:
        return False
    return actual_parts.issubset(expected_parts) or expected_parts.issubset(actual_parts)


def load_label_validator() -> Any:
    spec = importlib.util.spec_from_file_location("label_master_validate_label_output", LABEL_VALIDATOR_PATH)
    if spec is None or spec.loader is None:
        raise RuntimeError(f"无法加载 label-master 校验器：{LABEL_VALIDATOR_PATH}")
    module = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = module
    spec.loader.exec_module(module)
    manifest_path = module.DEFAULT_OUTPUT
    validator = module.LabelOutputValidator(module.load_manifest(manifest_path))
    return validator


def validate_target(target: str) -> dict[str, Any]:
    validator = load_label_validator()
    # 历史 TSV 中多行 function/Agent 目标常以字面量 `\n` 存在，
    # label-master 校验器需要真实换行才能按多行程序解析。
    return validator.validate(normalize_target(target).replace("\\n", "\n"))


def escape_regex_literal(text: str) -> str:
    return re.escape(text.strip())


def build_query_pattern(query: str, before_queries: list[str] | None = None) -> str:
    before_queries = before_queries or []
    parts: list[str] = []
    for before_query in before_queries:
        if before_query.strip():
            parts.append(f"beforeQuery#{escape_regex_literal(before_query)}")
    parts.append(f"query#{escape_regex_literal(query)}")
    return "^" + "#".join(parts) + "$"


def exact_tsv_line(device: str, query: str, target: str, dispatch_domain: str) -> str:
    return "\t".join([normalize_device(device), query.strip(), normalize_target(target), dispatch_domain.strip()])


def regex_tsv_line(device: str, pattern: str, target: str) -> str:
    return "\t".join([normalize_device(device), pattern.strip(), normalize_target(target)])


def parse_candidate_lines(text: str) -> list[list[str]]:
    rows = []
    for line in text.splitlines():
        if line.strip():
            rows.append(next(csv.reader([line], delimiter="\t")))
    return rows


def json_dumps(payload: Any) -> str:
    return json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True)
