from __future__ import annotations

import re
from datetime import date, timedelta

CITY_NAMES = (
    "北京",
    "上海",
    "广州",
    "深圳",
    "杭州",
    "南京",
    "苏州",
    "成都",
    "重庆",
    "天津",
    "武汉",
    "西安",
    "长沙",
    "郑州",
    "青岛",
    "厦门",
    "福州",
    "合肥",
    "济南",
    "沈阳",
    "大连",
    "宁波",
    "无锡",
)

MONTH_DAY_PATTERN = re.compile(r"(?P<month>\d{1,2})\s*月\s*(?P<day>\d{1,2})\s*(?:日|号)?")
ISO_DATE_PATTERN = re.compile(
    r"(?P<year>\d{4})[-/年](?P<month>\d{1,2})[-/月](?P<day>\d{1,2})(?:日)?"
)
ISO_DATE_RANGE_PATTERN = re.compile(
    r"(?P<start_year>\d{4})[-/年](?P<start_month>\d{1,2})[-/月](?P<start_day>\d{1,2})(?:日)?"
    r"(?:至|到|~|～|—|--)"
    r"(?P<end_year>\d{4})[-/年](?P<end_month>\d{1,2})[-/月](?P<end_day>\d{1,2})(?:日)?"
)
MONTH_DAY_RANGE_PATTERN = re.compile(
    r"(?P<start_month>\d{1,2})\s*月\s*(?P<start_day>\d{1,2})\s*(?:日|号)?"
    r"(?:至|到|~|～|—|--|-)"
    r"(?:(?P<end_month>\d{1,2})\s*月\s*)?(?P<end_day>\d{1,2})\s*(?:日|号)?"
)


class ApplicationFactResolver:
    @staticmethod
    def infer_expense_type(segment: str, task_type: str) -> str:
        compact = re.sub(r"\s+", "", segment)
        if re.search(r"招待|接待|餐饮|宴请|客户吃饭|业务餐", compact):
            return "entertainment"
        if re.search(r"出差|差旅|住宿|酒店|机票|航班|高铁|火车", compact):
            return "travel"
        if re.search(r"交通|出租车|的士|网约车|打车|地铁|公交", compact):
            return "transport" if task_type == "reimbursement" else "travel"
        return "travel" if task_type == "expense_application" else "other"

    @staticmethod
    def extract_time_range(segment: str, base_date: date) -> str:
        compact = re.sub(r"\s+", "", segment)
        if "昨天" in compact:
            return (base_date - timedelta(days=1)).isoformat()
        if "前天" in compact:
            return (base_date - timedelta(days=2)).isoformat()
        if "明天" in compact:
            return (base_date + timedelta(days=1)).isoformat()
        if "后天" in compact:
            return (base_date + timedelta(days=2)).isoformat()

        iso_range = ISO_DATE_RANGE_PATTERN.search(compact)
        if iso_range:
            start_date = ApplicationFactResolver.safe_date(
                int(iso_range.group("start_year")),
                int(iso_range.group("start_month")),
                int(iso_range.group("start_day")),
            )
            end_date = ApplicationFactResolver.safe_date(
                int(iso_range.group("end_year")),
                int(iso_range.group("end_month")),
                int(iso_range.group("end_day")),
            )
            if start_date and end_date:
                return f"{start_date} 至 {end_date}"

        month_day_range = MONTH_DAY_RANGE_PATTERN.search(compact)
        if month_day_range:
            start_month = int(month_day_range.group("start_month"))
            end_month = int(month_day_range.group("end_month") or start_month)
            start_date = ApplicationFactResolver.safe_date(
                base_date.year,
                start_month,
                int(month_day_range.group("start_day")),
            )
            end_date = ApplicationFactResolver.safe_date(
                base_date.year,
                end_month,
                int(month_day_range.group("end_day")),
            )
            if start_date and end_date:
                return f"{start_date} 至 {end_date}"

        iso_match = ISO_DATE_PATTERN.search(compact)
        if iso_match:
            return ApplicationFactResolver.safe_date(
                int(iso_match.group("year")),
                int(iso_match.group("month")),
                int(iso_match.group("day")),
            )

        month_day = MONTH_DAY_PATTERN.search(compact)
        if month_day:
            return ApplicationFactResolver.safe_date(
                base_date.year,
                int(month_day.group("month")),
                int(month_day.group("day")),
            )
        return ""

    @staticmethod
    def safe_date(year: int, month: int, day: int) -> str:
        try:
            return date(year, month, day).isoformat()
        except ValueError:
            return ""

    @staticmethod
    def extract_location(segment: str) -> str:
        compact = re.sub(r"\s+", "", segment)
        for prefix in ("去", "到", "赴", "前往"):
            match = re.search(fr"{prefix}({'|'.join(CITY_NAMES)})", compact)
            if match:
                return match.group(1)
        for city in CITY_NAMES:
            if city in compact:
                return city
        return ""

    @staticmethod
    def extract_reason(segment: str, task_type: str) -> str:
        cleaned = re.sub(r"\s+", "", segment).strip("，,。；; ")
        if task_type == "expense_application":
            normalized = strip_application_non_reason_tokens(cleaned)
            if normalized:
                return normalized
            match = re.search(r"(辅助|支持|协助|支撑|参加|拜访|调研|实施|部署|审核).+", cleaned)
            if match:
                return strip_trailing_connectors(match.group(0))
            reason = re.sub(r"^.*?(?:出差|差旅)", "", cleaned).strip("，,。；;的费用")
            return strip_trailing_connectors(reason) or cleaned
        cleaned = re.sub(r"^(?:我想要|我想|我要|还需要|需要|请帮我|帮我)?报销", "", cleaned)
        if not cleaned or cleaned in {"费用", "报销单", "报销流程"}:
            return ""
        cleaned = re.sub(r"^(?:昨天|前天|明天|后天|\d{1,2}月\d{1,2}(?:日|号)?)的?", "", cleaned)
        return cleaned.strip("，,。；; ")

    @staticmethod
    def extract_transport_mode(segment: str) -> str:
        compact = re.sub(r"\s+", "", segment)
        if re.search(r"高铁|动车|火车", compact):
            return "train"
        if re.search(r"飞机|机票|航班", compact):
            return "flight"
        if re.search(r"出租车|的士|网约车|打车", compact):
            return "taxi"
        if "交通" in compact:
            return "other"
        return ""


def strip_trailing_connectors(value: str) -> str:
    cleaned = str(value or "").strip("，,。；; ")
    return re.sub(r"(?:并且|而且|同时|另外|还需要|需要)$", "", cleaned).strip("，,。；; ")


def strip_application_non_reason_tokens(value: str) -> str:
    cleaned = str(value or "").strip("，,。；; ")
    if not cleaned:
        return ""

    preserved_transport_phrases: dict[str, str] = {}

    def preserve_transport_phrase(match: re.Match[str]) -> str:
        placeholder = f"__TRANSPORT_REASON_{len(preserved_transport_phrases)}__"
        preserved_transport_phrases[placeholder] = match.group(0)
        return placeholder

    cleaned = re.sub(
        r"(?:高铁|动车|火车|飞机|机票|航班|轮船|出租车|的士|网约车|打车|地铁|公交)往返",
        preserve_transport_phrase,
        cleaned,
    )
    cleaned = ISO_DATE_RANGE_PATTERN.sub("", cleaned)
    cleaned = MONTH_DAY_RANGE_PATTERN.sub("", cleaned)
    cleaned = ISO_DATE_PATTERN.sub("", cleaned)
    cleaned = MONTH_DAY_PATTERN.sub("", cleaned)
    cleaned = re.sub(r"昨天|前天|明天|后天|今天|上周|上月|下周|下月|近期|月底", "", cleaned)
    cleaned = re.sub(r"[0-9一二两三四五六七八九十]+天", "", cleaned)
    cleaned = re.sub(fr"(?:去|到|赴|前往)(?:{'|'.join(CITY_NAMES)})", "", cleaned)
    cleaned = re.sub(fr"(?:出差|差旅)(?:{'|'.join(CITY_NAMES)})", "", cleaned)
    cleaned = re.sub(fr"(?:{'|'.join(CITY_NAMES)})(?=出差|差旅)", "", cleaned)
    cleaned = re.sub(r"出差|差旅|费用申请|出差申请|差旅申请", "", cleaned)
    cleaned = re.sub(r"交通方式?|出行方式?", "", cleaned)
    cleaned = re.sub(r"高铁|动车|火车|飞机|机票|航班|轮船|出租车|的士|网约车|打车|地铁|公交", "", cleaned)
    cleaned = re.sub(
        r"保存草稿|存草稿|先保存|直接提交|提交申请|确认提交|提交审批|保存|提交|申请|发起|创建|我想要|我想|我要|请帮我|帮我",
        "",
        cleaned,
    )
    cleaned = re.sub(r"(^|[，,。；;、])(?:交通|出行)(?=$|[，,。；;、])", r"\1", cleaned)
    for placeholder, phrase in preserved_transport_phrases.items():
        cleaned = cleaned.replace(placeholder, phrase)
    cleaned = re.sub(r"[，,。；;、]+", "，", cleaned).strip("，,。；;、的 ")
    return strip_trailing_connectors(cleaned)


def resolve_application_facts(segment: str, task_type: str, base_date: date) -> dict[str, str]:
    fields = {
        "expense_type": ApplicationFactResolver.infer_expense_type(segment, task_type),
        "time_range": ApplicationFactResolver.extract_time_range(segment, base_date),
        "location": ApplicationFactResolver.extract_location(segment),
        "reason": ApplicationFactResolver.extract_reason(segment, task_type),
        "transport_mode": ApplicationFactResolver.extract_transport_mode(segment),
    }
    return {key: value for key, value in fields.items() if value}
