from __future__ import annotations

import re
from decimal import Decimal

from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext
from app.core.agent_enums import AgentAssetType
from app.models.employee import Employee
from app.schemas.reimbursement import (
    TravelReimbursementCalculatorRequest,
    TravelReimbursementCalculatorResponse,
)
from app.services.agent_assets import AgentAssetService
from app.services.expense_claims import ExpenseClaimService
from app.services.expense_rule_runtime import RuntimeTravelPolicy, ExpenseRuleRuntimeService

OTHER_REGION_LOCATION_KEYWORDS = {
    "河北",
    "石家庄",
    "唐山",
    "秦皇岛",
    "邯郸",
    "邢台",
    "保定",
    "张家口",
    "承德",
    "沧州",
    "廊坊",
    "衡水",
    "山西",
    "太原",
    "大同",
    "长治",
    "晋城",
    "晋中",
    "运城",
    "临汾",
    "吕梁",
    "内蒙古",
    "呼和浩特",
    "包头",
    "赤峰",
    "通辽",
    "鄂尔多斯",
    "辽宁",
    "鞍山",
    "抚顺",
    "本溪",
    "丹东",
    "锦州",
    "营口",
    "盘锦",
    "吉林",
    "长春",
    "吉林市",
    "四平",
    "通化",
    "白山",
    "松原",
    "延边",
    "黑龙江",
    "哈尔滨",
    "齐齐哈尔",
    "牡丹江",
    "佳木斯",
    "大庆",
    "江苏",
    "常州",
    "南通",
    "连云港",
    "淮安",
    "盐城",
    "扬州",
    "镇江",
    "泰州",
    "宿迁",
    "浙江",
    "温州",
    "嘉兴",
    "湖州",
    "绍兴",
    "金华",
    "衢州",
    "舟山",
    "台州",
    "丽水",
    "安徽",
    "芜湖",
    "蚌埠",
    "淮南",
    "马鞍山",
    "淮北",
    "铜陵",
    "安庆",
    "黄山",
    "滁州",
    "阜阳",
    "宿州",
    "六安",
    "亳州",
    "池州",
    "宣城",
    "福建",
    "泉州",
    "漳州",
    "莆田",
    "三明",
    "南平",
    "龙岩",
    "宁德",
    "江西",
    "南昌",
    "景德镇",
    "萍乡",
    "九江",
    "新余",
    "鹰潭",
    "赣州",
    "吉安",
    "宜春",
    "抚州",
    "上饶",
    "山东",
    "淄博",
    "枣庄",
    "东营",
    "烟台",
    "潍坊",
    "济宁",
    "泰安",
    "威海",
    "日照",
    "临沂",
    "德州",
    "聊城",
    "滨州",
    "菏泽",
    "河南",
    "洛阳",
    "开封",
    "平顶山",
    "安阳",
    "鹤壁",
    "新乡",
    "焦作",
    "濮阳",
    "许昌",
    "漯河",
    "三门峡",
    "南阳",
    "商丘",
    "信阳",
    "周口",
    "驻马店",
    "湖北",
    "黄石",
    "十堰",
    "宜昌",
    "襄阳",
    "鄂州",
    "荆门",
    "孝感",
    "荆州",
    "黄冈",
    "咸宁",
    "随州",
    "恩施",
    "湖南",
    "株洲",
    "湘潭",
    "衡阳",
    "邵阳",
    "岳阳",
    "常德",
    "张家界",
    "益阳",
    "郴州",
    "永州",
    "怀化",
    "娄底",
    "湘西",
    "广东",
    "惠州",
    "江门",
    "湛江",
    "茂名",
    "肇庆",
    "梅州",
    "汕尾",
    "河源",
    "阳江",
    "清远",
    "潮州",
    "揭阳",
    "云浮",
    "广西",
    "南宁",
    "柳州",
    "桂林",
    "梧州",
    "北海",
    "防城港",
    "钦州",
    "贵港",
    "玉林",
    "百色",
    "贺州",
    "河池",
    "来宾",
    "崇左",
    "海南",
    "儋州",
    "四川",
    "自贡",
    "攀枝花",
    "泸州",
    "德阳",
    "绵阳",
    "广元",
    "遂宁",
    "内江",
    "乐山",
    "南充",
    "眉山",
    "宜宾",
    "广安",
    "达州",
    "雅安",
    "巴中",
    "资阳",
    "阿坝",
    "甘孜",
    "凉山",
    "贵州",
    "贵阳",
    "遵义",
    "六盘水",
    "安顺",
    "毕节",
    "铜仁",
    "黔东南",
    "黔南",
    "黔西南",
    "云南",
    "曲靖",
    "玉溪",
    "保山",
    "昭通",
    "丽江",
    "普洱",
    "临沧",
    "楚雄",
    "红河",
    "文山",
    "西双版纳",
    "大理",
    "德宏",
    "怒江",
    "迪庆",
    "陕西",
    "宝鸡",
    "咸阳",
    "铜川",
    "渭南",
    "延安",
    "汉中",
    "榆林",
    "安康",
    "商洛",
    "甘肃",
    "兰州",
    "嘉峪关",
    "金昌",
    "白银",
    "天水",
    "武威",
    "张掖",
    "平凉",
    "酒泉",
    "庆阳",
    "定西",
    "陇南",
    "临夏",
    "甘南",
    "青海",
    "西宁",
    "海东",
    "海北",
    "黄南",
    "海南州",
    "果洛",
    "玉树",
    "海西",
    "宁夏",
    "银川",
    "石嘴山",
    "吴忠",
    "固原",
    "中卫",
}

OTHER_REGION_PROVINCE_KEYWORDS = {
    "河北",
    "山西",
    "内蒙古",
    "辽宁",
    "吉林",
    "黑龙江",
    "江苏",
    "浙江",
    "安徽",
    "福建",
    "江西",
    "山东",
    "河南",
    "湖北",
    "湖南",
    "广东",
    "广西",
    "海南",
    "四川",
    "贵州",
    "云南",
    "陕西",
    "甘肃",
    "青海",
    "宁夏",
    "新疆",
    "西藏",
    "台湾",
    "香港",
    "澳门",
}

AMBIGUOUS_PROVINCE_CITY_NAMES = {"吉林"}


class TravelReimbursementCalculatorService:
    def __init__(self, db: Session) -> None:
        self.db = db

    def calculate(
        self,
        payload: TravelReimbursementCalculatorRequest,
        current_user: CurrentUserContext,
    ) -> TravelReimbursementCalculatorResponse:
        days = max(1, int(payload.days))
        location = str(payload.location or "").strip()
        if not location:
            raise ValueError("请先填写出差地点。")

        policy = self._load_travel_policy()
        grade = self._resolve_grade(payload.grade, current_user)
        if not grade:
            raise ValueError("未识别到当前员工职级，请在个人信息中维护职级后再计算。")

        grade_band = ExpenseClaimService._resolve_travel_policy_band(grade)
        if not grade_band:
            raise ValueError(f"当前职级 {grade} 暂未匹配到差旅报销档位。")

        matched_city = self._resolve_city(location, policy)
        matched_other_region = "" if matched_city else self._resolve_other_region(location)
        if not matched_city and not matched_other_region:
            raise ValueError(f"出差地点“{location}”未识别为有效出差地区，请按真实省市或规则表地点重新填写。")
        city_tier = policy.city_tiers.get(matched_city, "tier_3") if matched_city else "tier_3"
        hotel_rate = self._resolve_hotel_rate(policy, grade_band, matched_city, city_tier)
        allowance_region = self._resolve_allowance_region(location, matched_city or matched_other_region)
        meal_rate = self._resolve_allowance_rate(policy, "meal", allowance_region)
        basic_rate = self._resolve_allowance_rate(policy, "basic", allowance_region)
        total_allowance_rate = self._resolve_total_allowance_rate(policy, allowance_region, meal_rate, basic_rate)

        hotel_amount = hotel_rate * Decimal(days)
        allowance_amount = total_allowance_rate * Decimal(days)
        total_amount = hotel_amount + allowance_amount
        band_label = policy.band_labels.get(grade_band, grade_band)
        rule_name = policy.standard_rule_name or policy.rule_name or "公司差旅费报销规则"
        rule_version = policy.standard_rule_version or policy.rule_version or ""
        display_city = matched_city or self._format_other_region_display(matched_other_region)
        formula_text = (
            f"住宿 {self._format_money(hotel_rate)} × {days} 天 + "
            f"补贴 {self._format_money(total_allowance_rate)} × {days} 天 = "
            f"{self._format_money(total_amount)}"
        )
        summary_text = (
            f"按《{rule_name}》{f'（{rule_version}）' if rule_version else ''}测算："
            f"当前职级 {grade} 对应 {band_label} 档，出差地点“{location}”匹配为“{display_city}”，"
            f"住宿标准 {self._format_money(hotel_rate)} 元/天，补贴区域为“{allowance_region}”，"
            f"补贴标准 {self._format_money(total_allowance_rate)} 元/天"
            f"（伙食 {self._format_money(meal_rate)} + 基本 {self._format_money(basic_rate)}）。"
            f"按 {days} 天计算，住宿合计 {self._format_money(hotel_amount)} 元，"
            f"补贴合计 {self._format_money(allowance_amount)} 元，"
            f"参考可报销总金额为 {self._format_money(total_amount)} 元。"
        )

        return TravelReimbursementCalculatorResponse(
            days=days,
            location=location,
            matched_city=display_city,
            city_tier=city_tier,
            grade=grade,
            grade_band=grade_band,
            grade_band_label=band_label,
            hotel_rate=hotel_rate,
            hotel_amount=hotel_amount,
            allowance_region=allowance_region,
            meal_allowance_rate=meal_rate,
            basic_allowance_rate=basic_rate,
            total_allowance_rate=total_allowance_rate,
            allowance_amount=allowance_amount,
            total_amount=total_amount,
            rule_name=rule_name,
            rule_version=rule_version,
            formula_text=formula_text,
            summary_text=summary_text,
        )

    def _load_travel_policy(self) -> RuntimeTravelPolicy:
        AgentAssetService(self.db).list_assets(asset_type=AgentAssetType.RULE.value)
        policy = ExpenseRuleRuntimeService(self.db).load_catalog().travel_policy
        if policy is None:
            raise ValueError("规则中心暂未配置差旅报销规则。")
        return policy

    def _resolve_grade(
        self,
        grade: str | None,
        current_user: CurrentUserContext,
    ) -> str:
        normalized_grade = str(grade or "").strip()
        if normalized_grade:
            return normalized_grade

        employee = self._resolve_current_employee(current_user)
        if employee is not None and str(employee.grade or "").strip():
            return str(employee.grade).strip()
        return ""

    @staticmethod
    def _resolve_other_region(location: str) -> str:
        normalized = re.sub(r"\s+", "", str(location or "").strip())
        if not normalized:
            return ""
        if any(keyword in normalized for keyword in ("国外", "境外", "海外")):
            return "国外"
        for keyword in ("香港", "澳门", "台湾", "港澳台", "西藏", "拉萨", "新疆", "乌鲁木齐"):
            if keyword in normalized:
                return keyword
        city_matches = []
        province_matches = []
        for keyword in OTHER_REGION_LOCATION_KEYWORDS:
            if not keyword or keyword not in normalized:
                continue
            if keyword in OTHER_REGION_PROVINCE_KEYWORDS:
                province_matches.append(keyword)
            else:
                city_matches.append(keyword)
        candidates = city_matches or province_matches
        if candidates:
            return sorted(candidates, key=len, reverse=True)[0]
        return ""

    @staticmethod
    def _format_other_region_display(region: str) -> str:
        normalized = str(region or "").strip()
        if not normalized:
            return ""
        if normalized in {"国外", "香港", "澳门", "台湾", "港澳台", "西藏", "拉萨", "新疆", "乌鲁木齐"}:
            return normalized
        return f"{normalized}（其他地区）"

    def _resolve_current_employee(self, current_user: CurrentUserContext) -> Employee | None:
        candidates = [
            str(current_user.username or "").strip(),
            str(current_user.name or "").strip(),
        ]
        normalized_candidates = [
            item
            for item in dict.fromkeys(candidate for candidate in candidates if candidate)
            if item
        ]
        if not normalized_candidates:
            return None

        for candidate in normalized_candidates:
            employee = self.db.scalar(
                select(Employee)
                .where(
                    or_(
                        func.lower(Employee.email) == candidate.lower(),
                        func.lower(Employee.employee_no) == candidate.lower(),
                    )
                )
                .limit(1)
            )
            if employee is not None:
                return employee

        for candidate in normalized_candidates:
            matches = list(
                self.db.scalars(
                    select(Employee)
                    .where(Employee.name == candidate)
                    .limit(2)
                ).all()
            )
            if len(matches) == 1:
                return matches[0]
        return None

    @staticmethod
    def _resolve_city(location: str, policy: RuntimeTravelPolicy) -> str:
        normalized = str(location or "").strip()
        if not normalized:
            return ""
        city_names = set(policy.city_tiers.keys())
        city_names.update(policy.hotel_city_limits.keys())
        for city in sorted(city_names, key=lambda item: len(item), reverse=True):
            if city in AMBIGUOUS_PROVINCE_CITY_NAMES and normalized != city and f"{city}市" not in normalized:
                continue
            if city and city in normalized:
                return city
        compact = re.sub(r"(省|市|区|县|自治州|特别行政区)$", "", normalized)
        for city in sorted(city_names, key=lambda item: len(item), reverse=True):
            if city in AMBIGUOUS_PROVINCE_CITY_NAMES and compact != city and f"{city}市" not in normalized:
                continue
            if city and city in compact:
                return city
        return ""

    @staticmethod
    def _resolve_hotel_rate(
        policy: RuntimeTravelPolicy,
        grade_band: str,
        matched_city: str,
        city_tier: str,
    ) -> Decimal:
        city_limits = policy.hotel_city_limits.get(matched_city, {}) if matched_city else {}
        if city_limits.get(grade_band) is not None:
            return Decimal(city_limits[grade_band])

        band_limits = policy.hotel_limits.get(grade_band, {})
        if band_limits.get(city_tier) is not None:
            return Decimal(band_limits[city_tier])
        if band_limits.get("tier_3") is not None:
            return Decimal(band_limits["tier_3"])
        return Decimal("0")

    @staticmethod
    def _resolve_allowance_region(location: str, matched_city: str) -> str:
        text = f"{location} {matched_city}".strip()
        if any(keyword in text for keyword in ("国外", "境外", "海外")):
            return "国外"
        if any(keyword in text for keyword in ("香港", "澳门", "台湾", "港澳台")):
            return "港澳台"
        if "乌鲁木齐" in text:
            return "新疆-乌鲁木齐"
        if "新疆" in text:
            return "新疆-其他"
        if "西藏" in text or "拉萨" in text:
            return "西藏"
        if any(keyword in text for keyword in ("北京", "上海", "天津", "重庆", "深圳", "珠海", "汕头", "厦门")):
            return "直辖市/特区"
        return "其他地区"

    @staticmethod
    def _resolve_allowance_rate(policy: RuntimeTravelPolicy, allowance_key: str, region: str) -> Decimal:
        limits = policy.allowance_limits.get(allowance_key, {})
        if limits.get(region) is not None:
            return Decimal(limits[region])
        if limits.get("其他地区") is not None:
            return Decimal(limits["其他地区"])
        return Decimal("0")

    def _resolve_total_allowance_rate(
        self,
        policy: RuntimeTravelPolicy,
        region: str,
        meal_rate: Decimal,
        basic_rate: Decimal,
    ) -> Decimal:
        total_limits = policy.allowance_limits.get("total", {})
        if total_limits.get(region) is not None:
            return Decimal(total_limits[region])
        if total_limits.get("其他地区") is not None:
            return Decimal(total_limits["其他地区"])
        return meal_rate + basic_rate

    @staticmethod
    def _format_money(value: Decimal | int | float | str) -> str:
        return f"{Decimal(str(value)).quantize(Decimal('0.01'))}"
