from __future__ import annotations

import re
from collections.abc import Iterable
from functools import lru_cache
from typing import Any

from app.services.user_agent_application_locations import (
    CITY_TO_PROVINCE,
    DIRECT_MUNICIPALITY_DISPLAY,
)

PLACEHOLDER_LOCATION_VALUES = {"", "待补充", "待确认", "未知", "暂无", "无", "null", "none"}
BUSINESS_ACTION_PATTERN = re.compile(r"(?:支撑|支持|辅助|部署|上线|实施|验收|项目)")
BUSINESS_OBJECT_PATTERN = re.compile(r"(?:服务器|系统|仿生产|生产环境|测试环境)")
SPECIFIC_ADDRESS_HINT_PATTERN = re.compile(r"""
    (?:省|市|区|县|自治州|州|镇|乡|街道|路|街|大道|园区|大厦|中心|基地|机场|车站|高铁站|火车站|酒店|楼|号)$
""", re.VERBOSE)
LOCATION_TAGS = {"LOC", "ns", "s"}
JIEBA_LOCATION_TAGS = {"ns"}
JIEBA_CUSTOM_WORDS = (
    "国网",
    "仿生产",
    "生产环境",
    "测试环境",
    "服务器",
    "部署",
    "辅助",
    "支撑",
    "支持",
    "上线",
    "实施",
    "验收",
)
ROUTE_LOCATION_PREFIX_PATTERN = re.compile(
    r"^(?P<prefix>.*?(?:出差|前往|去|到|赴))(?P<body>[\u4e00-\u9fa5].*)$"
)


def compact_application_location_text(value: object) -> str:
    text = re.sub(r"\s+", "", str(value or ""))
    text = re.sub(r"^(?:地点|业务地点|发生地点|目的地)[:：]", "", text)
    text = re.sub(r"^(?:去|到|赴|前往)", "", text)
    return text.strip("：:，,。；;、")


def validate_application_location_text(value: object) -> str:
    text = compact_application_location_text(value)
    if text.lower() in PLACEHOLDER_LOCATION_VALUES:
        return ""
    if not location_mixes_business_content(text):
        return ""
    return (
        f"地点“{text}”混入了业务事项，请填写真实出差地点，例如“上海”；"
        "业务背景请放在申请事由中。"
    )


def location_mixes_business_content(value: object) -> bool:
    text = compact_application_location_text(value)
    if text.lower() in PLACEHOLDER_LOCATION_VALUES:
        return False
    if _matches_business_location_pattern(text):
        return True
    return _lac_detects_business_location_mix(text)


def _matches_business_location_pattern(text: str) -> bool:
    if BUSINESS_ACTION_PATTERN.search(text):
        return True
    if BUSINESS_OBJECT_PATTERN.search(text) and not SPECIFIC_ADDRESS_HINT_PATTERN.search(text):
        return True
    return False


def _lac_detects_business_location_mix(text: str) -> bool:
    tokens = list(resolve_lac_tokens(text))
    if not tokens:
        return False
    has_location = any(tag in LOCATION_TAGS for _, tag in tokens)
    if not has_location:
        return False
    non_location_text = "".join(
        word
        for word, tag in tokens
        if tag not in LOCATION_TAGS and tag != "w"
    )
    return _matches_business_location_pattern(non_location_text)


@lru_cache(maxsize=1)
def _load_lac_analyzer() -> Any:
    try:
        from LAC import LAC  # type: ignore
    except Exception:
        return None
    try:
        return LAC(mode="lac")
    except Exception:
        return None


def resolve_lac_tokens(text: str) -> Iterable[tuple[str, str]]:
    analyzer = _load_lac_analyzer()
    if analyzer is None:
        return []
    try:
        result = analyzer.run(text)
    except Exception:
        return []
    return _parse_lac_result(result)


@lru_cache(maxsize=1)
def _load_jieba_posseg() -> Any:
    try:
        import jieba
        import jieba.posseg as pseg
    except Exception:
        return None
    for word in _iter_jieba_custom_words():
        jieba.add_word(word, freq=100000)
    return pseg


def clear_application_location_semantic_caches() -> int:
    cleared_count = _load_lac_analyzer.cache_info().currsize
    cleared_count += _load_jieba_posseg.cache_info().currsize
    _load_lac_analyzer.cache_clear()
    _load_jieba_posseg.cache_clear()
    return cleared_count


def _iter_jieba_custom_words() -> Iterable[str]:
    yield from JIEBA_CUSTOM_WORDS
    yield from DIRECT_MUNICIPALITY_DISPLAY
    yield from CITY_TO_PROVINCE


def resolve_jieba_tokens(text: str) -> list[tuple[str, str]]:
    posseg = _load_jieba_posseg()
    if posseg is None:
        return []
    try:
        return [
            (str(item.word or "").strip(), str(item.flag or "").strip())
            for item in posseg.cut(str(text or ""), HMM=True)
            if str(item.word or "").strip()
        ]
    except Exception:
        return []


def strip_route_location_prefix_with_jieba(value: object) -> str:
    text = str(value or "").strip()
    match = ROUTE_LOCATION_PREFIX_PATTERN.search(text)
    if not match:
        return text

    body = match.group("body").strip()
    tokens = resolve_jieba_tokens(body)
    if not tokens:
        return text
    first_word, first_tag = tokens[0]
    if not _is_jieba_location_token(first_word, first_tag):
        return text
    return body[len(first_word) :].strip(" ：:，,。；;、")


def _is_jieba_location_token(word: str, tag: str) -> bool:
    if tag in JIEBA_LOCATION_TAGS:
        return True
    return word in DIRECT_MUNICIPALITY_DISPLAY or word in CITY_TO_PROVINCE


def _parse_lac_result(result: Any) -> list[tuple[str, str]]:
    if (
        isinstance(result, (list, tuple))
        and len(result) == 2
        and isinstance(result[0], list)
        and isinstance(result[1], list)
    ):
        return [
            (str(word or "").strip(), str(tag or "").strip())
            for word, tag in zip(result[0], result[1], strict=False)
            if str(word or "").strip()
        ]
    if isinstance(result, list) and all(
        isinstance(item, (list, tuple)) and len(item) >= 2
        for item in result
    ):
        return [
            (str(item[0] or "").strip(), str(item[1] or "").strip())
            for item in result
            if str(item[0] or "").strip()
        ]
    return []
