from __future__ import annotations

import json
import re
from datetime import date
from typing import Any

from app.services.runtime_chat import RuntimeChatService


CITY_NAMES = [
    "北京",
    "上海",
    "广州",
    "深圳",
    "杭州",
    "南京",
    "成都",
    "武汉",
    "重庆",
    "天津",
    "苏州",
    "西安",
    "长沙",
    "郑州",
    "青岛",
    "厦门",
    "福州",
    "合肥",
    "济南",
    "沈阳",
    "大连",
    "宁波",
    "温州",
    "无锡",
    "常州",
    "昆明",
    "贵阳",
    "南宁",
    "南昌",
    "太原",
    "石家庄",
    "哈尔滨",
    "长春",
    "兰州",
    "银川",
    "西宁",
    "乌鲁木齐",
    "呼和浩特",
    "海口",
    "三亚",
    "佛山",
    "东莞",
    "珠海",
    "惠州",
    "嘉兴",
    "绍兴",
    "金华",
    "台州",
    "泉州",
    "烟台",
    "徐州",
    "扬州",
    "南通",
    "镇江",
    "洛阳",
    "襄阳",
    "宜昌",
]


def refine_simulation_values_with_model(
    db: Any,
    fields: list[dict[str, str]],
    *,
    message: str,
    attachments: list[dict[str, Any]],
) -> dict[str, Any]:
    if not fields or not attachments or db is None:
        return {}

    compact_attachments = [
        {
            "name": item.get("name") or "",
            "document_type_label": item.get("document_type_label") or "",
            "scene_label": item.get("scene_label") or "",
            "summary": item.get("summary") or "",
            "ocr_text": str(item.get("ocr_text") or "")[:2500],
            "document_fields": list(item.get("document_fields") or [])[:20],
        }
        for item in attachments[:4]
    ]
    messages = [
        {
            "role": "system",
            "content": (
                "你是风险规则仿真测试的票据字段过滤器。只根据用户输入、OCR 文本和结构化字段，"
                "把信息归一化到给定字段 key。不要判断风险，不要编造不存在的信息。只输出 JSON。"
            ),
        },
        {
            "role": "user",
            "content": json.dumps(
                {
                    "available_fields": fields,
                    "user_message": str(message or ""),
                    "attachments": compact_attachments,
                    "output_shape": {
                        "field_values": {
                            "claim.location": "城市或地点",
                            "attachment.route_cities": ["出发城市", "到达城市"],
                        }
                    },
                    "rules": [
                        "字段值必须来自 OCR 或用户输入。",
                        "车票路线要拆成城市数组，例如 上海虹桥-武汉 -> [\"上海\", \"武汉\"]。",
                        "没有把握的字段不要返回。",
                    ],
                },
                ensure_ascii=False,
            ),
        },
    ]
    try:
        answer = RuntimeChatService(db).complete(
            messages,
            max_tokens=700,
            temperature=0.0,
            timeout_seconds=8,
            max_attempts=1,
        )
    except Exception:
        return {}

    payload = extract_json_payload(answer)
    if not isinstance(payload, dict):
        return {}
    field_values = payload.get("field_values") if isinstance(payload.get("field_values"), dict) else {}
    allowed = {field["key"] for field in fields}
    return {
        key: value
        for key, value in field_values.items()
        if key in allowed and has_meaningful_value(value)
    }


def derive_attachment_field_value(short_key: str, attachment: dict[str, Any]) -> Any:
    fields = [field for field in list(attachment.get("document_fields") or []) if isinstance(field, dict)]
    text_parts = [
        str(attachment.get("summary") or ""),
        str(attachment.get("ocr_text") or ""),
        *[str(field.get("value") or "") for field in fields],
    ]
    corpus = "\n".join(part for part in text_parts if part)

    if short_key == "route_cities":
        for field in fields:
            key = str(field.get("key") or "").strip().lower()
            label = str(field.get("label") or "").strip()
            if looks_like_route_field(key, label):
                cities = extract_route_cities(str(field.get("value") or ""))
                if cities:
                    return cities
        cities = extract_route_cities(corpus)
        return cities or None

    if short_key in {"hotel_city", "city"}:
        for field in fields:
            key = str(field.get("key") or "").strip().lower()
            label = str(field.get("label") or "").strip()
            if looks_like_route_field(key, label):
                continue
            if looks_like_city_field(f"{key} {label}"):
                cities = extract_city_mentions(str(field.get("value") or ""))
                if cities:
                    return cities[0]
                value = str(field.get("value") or "").strip()
                if value:
                    return value
        return None

    alias_keys = {
        "invoice_no": {"invoice_no", "invoice_number", "ticket_number", "order_no", "order_number"},
        "issue_date": {"issue_date", "invoice_date", "date", "trip_date", "travel_date"},
        "stay_start_date": {"stay_start_date", "check_in_date", "arrival_date", "入住日期"},
        "stay_end_date": {"stay_end_date", "check_out_date", "departure_date", "离店日期"},
        "amount": {"amount", "total_amount", "payment_amount", "paid_amount"},
        "goods_name": {"goods_name", "service_name", "item_name", "merchant_name"},
    }.get(short_key, set())
    if alias_keys:
        for field in fields:
            key = str(field.get("key") or "").strip().lower()
            label = str(field.get("label") or "").strip()
            if (
                key in alias_keys
                or field_matches_simulation_key(key, label, short_key, short_key)
            ) and has_meaningful_value(field.get("value")):
                return field.get("value")
    return None


def field_matches_simulation_key(
    candidate_key: str,
    candidate_label: str,
    short_key: str,
    target_label: str,
) -> bool:
    compact_candidate = candidate_key.replace("_", "")
    compact_target = short_key.replace("_", "").lower()
    if compact_target and compact_target in compact_candidate:
        return True
    label_text = f"{candidate_label} {target_label}"
    label_map = {
        "invoice_no": ("发票号", "发票号码", "票号"),
        "hotel_city": ("住宿城市", "酒店城市", "酒店地点", "住宿", "酒店"),
        "route_cities": ("行程", "路线", "目的地", "出差城市"),
        "goods_name": ("品名", "商品", "服务名称"),
        "amount": ("金额", "价税合计", "合计"),
        "issue_date": ("日期", "开票日期", "发票日期"),
        "stay_start_date": ("入住日期", "住宿开始", "入住时间", "开始日期"),
        "stay_end_date": ("离店日期", "退房日期", "住宿结束", "结束日期"),
    }
    return any(token in label_text for token in label_map.get(short_key, ()))


def extract_json_payload(response_text: str | None) -> dict[str, Any] | None:
    if not response_text:
        return None
    cleaned = re.sub(
        r"<think>.*?</think>",
        "",
        str(response_text),
        flags=re.DOTALL | re.IGNORECASE,
    ).strip()
    if not cleaned:
        return None
    candidates: list[str] = []
    fenced_match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", cleaned, flags=re.DOTALL)
    if fenced_match:
        candidates.append(fenced_match.group(1))
    start = cleaned.find("{")
    end = cleaned.rfind("}")
    if start >= 0 and end > start:
        candidates.append(cleaned[start : end + 1])
    candidates.append(cleaned)
    for candidate in candidates:
        try:
            parsed = json.loads(candidate)
        except json.JSONDecodeError:
            continue
        if isinstance(parsed, dict):
            return parsed
    return None


def extract_city_mentions(text: str) -> list[str]:
    content = str(text or "")
    if not content:
        return []
    pattern = "|".join(re.escape(city) for city in CITY_NAMES)
    found: list[str] = []
    for match in re.finditer(pattern, content):
        city = match.group(0)
        if city not in found:
            found.append(city)
    return found


def extract_route_cities(text: str) -> list[str]:
    mentions = extract_city_mentions(text)
    if mentions:
        return mentions
    route_text = str(text or "")
    if not route_text:
        return []
    fragments = re.split(r"(?:->|→|—|--|-|至|到|开往|前往|赴)", route_text)
    cities: list[str] = []
    for fragment in fragments:
        candidate = normalize_city_token(fragment)
        if candidate and candidate not in cities:
            cities.append(candidate)
    return cities[:4]


def normalize_city_token(value: str) -> str:
    text = re.sub(r"[^\u4e00-\u9fff]", "", str(value or ""))
    if not text:
        return ""
    for suffix in (
        "火车站",
        "高铁站",
        "汽车站",
        "机场",
        "虹桥",
        "东站",
        "西站",
        "南站",
        "北站",
        "站",
        "市",
    ):
        text = text.replace(suffix, "")
    if 2 <= len(text) <= 6:
        return text
    return ""


def extract_amount(text: str) -> str:
    match = re.search(r"(\d{2,8}(?:\.\d{1,2})?)\s*(?:元|块|人民币|CNY)?", text, re.IGNORECASE)
    return match.group(1) if match else ""


def extract_iso_date(text: str) -> str:
    match = re.search(r"(20\d{2})[-/.年](\d{1,2})[-/.月](\d{1,2})", text)
    if not match:
        return ""
    year, month, day = (int(part) for part in match.groups())
    try:
        return date(year, month, day).isoformat()
    except ValueError:
        return ""


def extract_invoice_no(text: str) -> str:
    match = re.search(r"(?:发票号|发票号码|票号)[:：\s]*([A-Z0-9-]{6,32})", text, re.IGNORECASE)
    return match.group(1) if match else ""


def infer_item_type(text: str) -> str:
    if not text:
        return ""
    if any(keyword in text for keyword in ("酒店", "住宿", "宾馆")):
        return "住宿费"
    if any(keyword in text for keyword in ("机票", "航班", "火车", "高铁", "打车")):
        return "交通费"
    if any(keyword in text for keyword in ("餐饮", "餐费", "招待")):
        return "餐饮费"
    return "差旅费"


def infer_goods_name(text: str) -> str:
    if not text:
        return ""
    if any(keyword in text for keyword in ("酒店", "住宿", "宾馆")):
        return "住宿服务"
    if any(keyword in text for keyword in ("机票", "航班", "火车", "高铁", "打车")):
        return "交通服务"
    if any(keyword in text for keyword in ("餐饮", "餐费", "招待")):
        return "餐饮服务"
    return "报销服务"


def looks_like_city_field(field_key: str) -> bool:
    lowered = field_key.lower()
    return "city" in lowered or "location" in lowered or lowered.endswith("route_cities")


def looks_like_route_field(candidate_key: str, candidate_label: str = "") -> bool:
    text = f"{candidate_key} {candidate_label}".lower()
    return any(
        token in text
        for token in (
            "route",
            "trip_route",
            "travel_route",
            "from_to",
            "origin_destination",
            "行程",
            "路线",
            "出发",
            "到达",
            "目的地",
            "车次",
        )
    )


def has_meaningful_value(value: Any) -> bool:
    if value is None:
        return False
    if isinstance(value, str):
        return bool(value.strip())
    if isinstance(value, (list, tuple, set, dict)):
        return bool(value)
    return True
