#!/usr/bin/env python3
"""Normalize Accio Work / Alibaba inquiry scrape output for scoring.

This adapter accepts the common output shape used by inquiry scraping skills:
either a list of inquiry records or a dict containing an ``inquiries`` list.
It converts available list/detail fields into the schema consumed by
``score_inquiry_v3.py``. Missing fields are left empty on purpose; scoring should
surface them instead of fabricating buyer facts.
"""

from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional


DEFAULT_RULES: Dict[str, Any] = {
    "target_country_tiers": {
        "target": [],
        "acceptable": [],
        "low_priority": [],
        "high_risk": [],
    },
    "target_products": [],
    "acceptable_products": [],
    "default_moq": 1000,
}

QUANTITY_PATTERNS = [
    re.compile(r"\b(\d{1,3}(?:,\d{3})+|\d+)\s*(?:pcs|pieces|units|sets|个|件|套)\b", re.I),
    re.compile(r"\b(\d+(?:\.\d+)?)\s*k\s*(?:pcs|pieces|units|sets)?\b", re.I),
    re.compile(r"(?:quantity|qty|数量|采购量|订购量|order)\D{0,12}(\d{1,3}(?:,\d{3})+|\d+)", re.I),
]

CERT_PATTERNS = [
    (re.compile(r"\brohs\b", re.I), "RoHS"),
    (re.compile(r"\bce\b", re.I), "CE"),
    (re.compile(r"\bfda\b", re.I), "FDA"),
    (re.compile(r"\biso\b", re.I), "ISO"),
    (re.compile(r"food\s*contact|食品接触|食品级", re.I), "food contact"),
]

TIMELINE_PATTERN = re.compile(
    r"(asap|urgent|this month|next month|this week|next week|within \d+\s*(?:days|weeks)|"
    r"本周|下周|本月|下月|尽快|加急|\d+\s*(?:天|周|个月)内)",
    re.I,
)

RISK_PATTERNS = [
    (re.compile(r"phishing|钓鱼|verify your account|login link", re.I), "phishing_link"),
    (re.compile(r"advertis(?:e|ing)|推广|营销服务|seo service", re.I), "advertising"),
    (re.compile(r"competitor|同行|套价", re.I), "competitor_quote"),
    (re.compile(r"attachment|附件|invoice\.|\.exe|\.scr", re.I), "suspicious_attachment"),
    (re.compile(r"tomorrow|24\s*hours|一天内|明天发货|马上交货", re.I), "unrealistic_delivery"),
]


def read_json(path: Path) -> Any:
    return json.loads(path.read_text(encoding="utf-8"))


def write_json(path: Optional[Path], payload: Any) -> None:
    text = json.dumps(payload, ensure_ascii=False, indent=2)
    if path:
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(text + "\n", encoding="utf-8")
    else:
        print(text)


def deep_merge(base: Dict[str, Any], override: Optional[Dict[str, Any]]) -> Dict[str, Any]:
    merged = json.loads(json.dumps(base, ensure_ascii=False))
    if not isinstance(override, dict):
        return merged
    for key, value in override.items():
        if isinstance(value, dict) and isinstance(merged.get(key), dict):
            merged[key].update(value)
        else:
            merged[key] = value
    return merged


def normalize_country(value: Any) -> str:
    if isinstance(value, dict):
        return str(value.get("name") or value.get("country") or "").strip()
    return str(value or "").strip()


def country_tier(country: str, rules: Dict[str, Any]) -> str:
    country_key = country.strip().lower()
    tiers = rules.get("target_country_tiers") or {}
    for tier in ("target", "acceptable", "low_priority", "high_risk"):
        values = [str(v).strip().lower() for v in tiers.get(tier, [])]
        if country_key and country_key in values:
            return tier
    return ""


def normalize_level(value: Any) -> Optional[str]:
    text = str(value or "").upper().strip()
    match = re.search(r"L[1-4]\+?", text)
    if not match:
        return None
    level = match.group(0)
    return "L1" if level == "L1+" else level


def flatten_messages(record: Dict[str, Any]) -> List[Dict[str, Any]]:
    detail = record.get("detail") or {}
    messages = detail.get("messages") or record.get("messages") or []
    return messages if isinstance(messages, list) else []


def message_text(record: Dict[str, Any]) -> str:
    parts: List[str] = []
    for field in ("product", "last_message", "category", "buyer_name"):
        value = record.get(field)
        if value:
            parts.append(str(value))
    for message in flatten_messages(record):
        if isinstance(message, dict):
            body = message.get("text") or message.get("content") or ""
            if body:
                parts.append(str(body))
    return " ".join(parts)


def conversation_excerpt(record: Dict[str, Any], limit: int = 260) -> str:
    texts = []
    for message in flatten_messages(record)[:5]:
        if isinstance(message, dict):
            speaker = str(message.get("speaker") or "").strip()
            body = str(message.get("text") or message.get("content") or "").strip()
            if body:
                texts.append(f"{speaker}: {body}" if speaker else body)
    if not texts and record.get("last_message"):
        texts.append(str(record.get("last_message")))
    text = " / ".join(texts)
    return text[:limit]


def extract_quantity(text: str) -> Optional[int]:
    candidates: List[int] = []
    for pattern in QUANTITY_PATTERNS:
        for match in pattern.finditer(text):
            raw = match.group(1).replace(",", "")
            value = float(raw) * 1000 if "k" in match.group(0).lower() else float(raw)
            if value > 0:
                candidates.append(int(value))
    return max(candidates) if candidates else None


def detect_certification(text: str) -> Optional[str]:
    found = []
    for pattern, label in CERT_PATTERNS:
        if pattern.search(text):
            found.append(label)
    return ", ".join(dict.fromkeys(found)) if found else None


def detect_timeline(text: str) -> Optional[str]:
    match = TIMELINE_PATTERN.search(text)
    return match.group(0) if match else None


def detect_risks(text: str) -> List[str]:
    risks = []
    for pattern, label in RISK_PATTERNS:
        if pattern.search(text):
            risks.append(label)
    return list(dict.fromkeys(risks))


def product_match(product: str, rules: Dict[str, Any]) -> str:
    product_text = product.lower()
    core = [str(v).lower() for v in rules.get("target_products") or []]
    acceptable = [str(v).lower() for v in rules.get("acceptable_products") or []]
    if any(term and term in product_text for term in core):
        return "core"
    if any(term and term in product_text for term in acceptable):
        return "acceptable"
    return ""


def source_label(record: Dict[str, Any]) -> str:
    text = str(record.get("category") or record.get("product") or record.get("source") or "").lower()
    if "tm" in text:
        return "TM"
    if "rfq" in text:
        return "RFQ"
    return "inquiry"


def _coerce_int(value: Any) -> Optional[int]:
    """字符串/数字 → int；None/空/无法解析 → None。"""
    if value is None or value == "":
        return None
    if isinstance(value, bool):
        return int(value)
    if isinstance(value, (int, float)):
        try:
            return int(value)
        except (ValueError, OverflowError):
            return None
    text = str(value).strip().replace(",", "")
    if not text:
        return None
    match = re.search(r"\d+", text)
    if not match:
        return None
    try:
        return int(match.group(0))
    except ValueError:
        return None


def _months_since(date_str: Any, today_str: Optional[str] = None) -> Optional[int]:
    """YYYY-MM-DD → 距今月数。无法解析返回 None。"""
    if not date_str:
        return None
    from datetime import date
    try:
        s = str(date_str).strip()[:10]
        y, m, d = [int(x) for x in s.split("-")]
        reg = date(y, m, d)
    except (ValueError, TypeError):
        return None
    if today_str:
        try:
            ty, tm, td = [int(x) for x in today_str.split("-")]
            today = date(ty, tm, td)
        except (ValueError, TypeError):
            today = date.today()
    else:
        today = date.today()
    months = (today.year - reg.year) * 12 + (today.month - reg.month)
    if today.day < reg.day:
        months -= 1
    return max(0, months)


def _parse_industry_match(industries_raw: Any, rules: Dict[str, Any]) -> Optional[str]:
    """most_inquired_industries 与本店主营/可做品类比对。
    返回 "match" / "partial" / "mismatch" / None（缺失）。
    """
    if not industries_raw:
        return None
    text = str(industries_raw).lower()
    core = [str(v).lower() for v in rules.get("target_products") or []]
    acceptable = [str(v).lower() for v in rules.get("acceptable_products") or []]
    # 本店主营关键词
    own = set(core + acceptable + ["pc", "computer", "laptop", "monitor", "notebook"])
    # 行业关键词通常用 | 或 , 分割
    parts = re.split(r"[|,;/]+", text)
    parts = [p.strip() for p in parts if p.strip()]
    if not parts:
        return None
    hits = 0
    for part in parts:
        for kw in own:
            if kw and kw in part:
                hits += 1
                break
    if hits >= 2:
        return "match"
    if hits == 1:
        return "partial"
    return "mismatch"


def _parse_buyer_features(features_raw: Any) -> List[str]:
    if not features_raw:
        return []
    if isinstance(features_raw, list):
        return [str(f).strip() for f in features_raw if f]
    text = str(features_raw).strip()
    if not text:
        return []
    # 中文标签通常空格分隔
    return [p.strip() for p in re.split(r"[\s,，;；]+", text) if p.strip()]


def normalize_record(record: Dict[str, Any], rules: Dict[str, Any]) -> Dict[str, Any]:
    text = message_text(record)
    country = normalize_country(record.get("buyer_country") or record.get("country"))
    product = str(record.get("product") or record.get("product_name") or "").strip()
    company = record.get("company") if isinstance(record.get("company"), dict) else {}
    quantity = _coerce_int(record.get("quantity"))
    if quantity is None:
        quantity = extract_quantity(text)
    demand = record.get("demand") if isinstance(record.get("demand"), dict) else {}
    # 客户面板增量字段（v2 新增）
    panel = record.get("customer_panel") if isinstance(record.get("customer_panel"), dict) else {}
    behavior = record.get("behavior_90d") if isinstance(record.get("behavior_90d"), dict) else {}
    today_str = rules.get("_today")  # 用于固定快照时间下的 months 计算
    account_registered_months = (
        _coerce_int(record.get("account_registered_months"))
        or _months_since(panel.get("account_registered_date"), today_str)
    )
    normalized = {
        "inquiry_id": record.get("inquiry_id") or record.get("id") or record.get("message_id"),
        "received_at": record.get("create_time") or record.get("received_at"),
        "updated_at": record.get("update_time") or record.get("last_update_time"),
        "source": source_label(record),
        "assignee": record.get("assignee") or record.get("owner"),
        "buyer_name": record.get("buyer_name") or record.get("sender_name"),
        "buyer_country": country,
        "country_tier": record.get("country_tier") or country_tier(country, rules),
        "buyer_l_level": normalize_level(record.get("buyer_l_level") or record.get("user_level") or panel.get("buyer_level")),
        "account_registered_months": account_registered_months,
        "product": product,
        "product_match": record.get("product_match") or product_match(product, rules),
        "quantity": quantity,
        "moq": record.get("moq") or rules.get("default_moq"),
        # ===== v2 新增：客户面板顶部字段 =====
        "business_type": panel.get("business_type") or record.get("business_type"),
        "buyer_tags": panel.get("buyer_tags") or record.get("buyer_tags") or [],
        "buyer_features": _parse_buyer_features(panel.get("buyer_features") or record.get("buyer_features")),
        # ===== v2 新增：近 90 天行为数据 =====
        "behavior_90d": {
            "product_view_count": _coerce_int(behavior.get("product_view_count") or behavior.get("product_view_count_90d")),
            "valid_inquiry_count": _coerce_int(behavior.get("valid_inquiry_count") or behavior.get("valid_inquiry_count_90d")),
            "valid_rfq_count": _coerce_int(behavior.get("valid_rfq_count") or behavior.get("valid_rfq_count_90d")),
            "login_days": _coerce_int(behavior.get("login_days") or behavior.get("login_days_90d")),
            "spam_inquiry_count": _coerce_int(behavior.get("spam_inquiry_count") or behavior.get("spam_inquiry_count_90d")),
            "blacklisted_count": _coerce_int(behavior.get("blacklisted_count") or behavior.get("blacklisted_count_90d")),
            "most_inquired_industries": behavior.get("most_inquired_industries"),
            "industry_match": _parse_industry_match(
                behavior.get("most_inquired_industries"), rules
            ),
        },
        # ===== v2 新增：后台原生风险标签（区别于 risk_signals） =====
        "risk_labels_from_backend": record.get("risk_labels_from_backend") or [],
        "demand": {
            "spec": demand.get("spec") or (product if product else None),
            "use_case": demand.get("use_case"),
            "target_market": demand.get("target_market") or country or None,
            "certification": demand.get("certification") or detect_certification(text),
            "timeline": demand.get("timeline") or detect_timeline(text),
        },
        "company": {
            "name": company.get("name") or record.get("company_name"),
            "website": company.get("website") or record.get("website"),
            "email_domain_match": company.get("email_domain_match"),
            "phone": company.get("phone") or record.get("phone"),
        },
        "customs_data": record.get("customs_data") or {"status": "none", "note": None},
        "conversation_excerpt": conversation_excerpt(record),
        "risk_signals": list(dict.fromkeys((record.get("risk_signals") or []) + detect_risks(text))),
        "status_context": record.get("status_context") or {"is_re_evaluation": False},
        "raw_source_meta": {
            "category": record.get("category"),
            "detail_url": record.get("detail_url"),
            "message_count": (record.get("detail") or {}).get("message_count")
            or (record.get("detail") or {}).get("stats", {}).get("message_count"),
            "buyer_product_ids": record.get("buyer_product_ids") or record.get("product_ids"),
        },
    }
    return normalized


def load_records(payload: Any) -> List[Dict[str, Any]]:
    if isinstance(payload, list):
        return [item for item in payload if isinstance(item, dict)]
    if isinstance(payload, dict):
        items = payload.get("inquiries") or payload.get("results") or []
        return [item for item in items if isinstance(item, dict)]
    raise ValueError("input must be a list or a dict containing inquiries/results")


def main() -> None:
    parser = argparse.ArgumentParser(description="Normalize AW Alibaba inquiry output for score_inquiry_v3.py")
    parser.add_argument("input", help="AW / Alibaba inquiry JSON")
    parser.add_argument("--rules", help="customer rules JSON; overrides rules in input")
    parser.add_argument("--output", "-o", help="output normalized JSON path; stdout if omitted")
    args = parser.parse_args()

    input_path = Path(args.input)
    payload = read_json(input_path)
    input_rules = payload.get("customer_rules") if isinstance(payload, dict) else {}
    if args.rules:
        input_rules = deep_merge(input_rules if isinstance(input_rules, dict) else {}, read_json(Path(args.rules)))
    rules = deep_merge(DEFAULT_RULES, input_rules)
    records = load_records(payload)
    output = {
        "schema_version": "0.2",
        "description": "Normalized from Accio Work / Alibaba inquiry scrape output for score_inquiry_v3.py.",
        "customer_rules": rules,
        "source_meta": {
            "source_file": str(input_path),
            "source_format": "dict.inquiries" if isinstance(payload, dict) and "inquiries" in payload else type(payload).__name__,
            "input_count": len(records),
        },
        "inquiries": [normalize_record(record, rules) for record in records],
    }
    write_json(Path(args.output) if args.output else None, output)


if __name__ == "__main__":
    try:
        main()
    except Exception as exc:
        print(json.dumps({"status": "error", "error": str(exc)}, ensure_ascii=False), file=sys.stderr)
        raise
