from __future__ import annotations

import re
from dataclasses import dataclass

from pydantic import BaseModel, ConfigDict, Field

from app.schemas.ontology import OntologyIntent, OntologyScenario
from app.services.expense_type_keywords import (
    EXPENSE_TYPE_LABEL_BY_CODE,
    build_expense_type_keyword_map,
)

DATE_RANGE_PATTERN = re.compile(
    r"(?P<start>\d{4}-\d{1,2}-\d{1,2})\s*(?:到|至|~|-)\s*(?P<end>\d{4}-\d{1,2}-\d{1,2})"
)
EXPLICIT_MONTH_PATTERN = re.compile(r"(?P<year>\d{4})年(?P<month>\d{1,2})月")
EXPLICIT_DATE_PATTERN = re.compile(
    r"(?P<year>\d{4})[年/-](?P<month>\d{1,2})[月/-](?P<day>\d{1,2})日?"
)
MONTH_DAY_RANGE_PATTERN = re.compile(
    r"(?P<start_month>\d{1,2})月(?P<start_day>\d{1,2})日?\s*(?:到|至|~|-)\s*"
    r"(?P<end_month>\d{1,2})月(?P<end_day>\d{1,2})日?"
)
MONTH_DAY_PATTERN = re.compile(r"(?P<month>\d{1,2})月(?P<day>\d{1,2})日?")
AMOUNT_PATTERN = re.compile(
    r"(?P<prefix>超过|大于|高于|不少于|不低于|小于|低于|少于|至多|不超过|<=|>=|<|>|＝|=)?\s*"
    r"(?P<value>\d+(?:\.\d+)?)\s*(?P<unit>万元|万|元)?"
)
TOP_N_PATTERN = re.compile(r"(?:top|TOP|前|最高的?|最低的?)\s*(?P<top>\d+)")

SCENARIO_KEYWORDS = {
    "expense": (
        ("报销", 0.20),
        ("报销单", 0.20),
        ("单据报销", 0.18),
        ("报账", 0.20),
        ("差旅", 0.20),
        ("费用", 0.14),
        ("发票", 0.14),
        ("票据", 0.12),
        ("借款", 0.12),
        ("住宿", 0.10),
        ("餐费", 0.10),
        ("招待", 0.18),
        ("招待费", 0.18),
        ("花销", 0.16),
        ("花了", 0.14),
        ("支出", 0.14),
        ("垫付", 0.14),
    ),
    "accounts_receivable": (
        ("应收", 0.22),
        ("回款", 0.20),
        ("收款", 0.18),
        ("账龄", 0.18),
        ("客户欠款", 0.22),
    ),
    "accounts_payable": (
        ("应付", 0.22),
        ("付款", 0.20),
        ("请款", 0.18),
        ("供应商", 0.20),
        ("待付", 0.16),
        ("打款", 0.18),
    ),
    "budget": (
        ("预算中心", 0.28),
        ("预算管理", 0.26),
        ("预算编制", 0.24),
        ("预算", 0.20),
        ("预算额度", 0.22),
        ("预算金额", 0.22),
        ("可用预算", 0.22),
        ("剩余预算", 0.22),
        ("预算余额", 0.20),
        ("预算占用", 0.22),
        ("预算预占", 0.22),
        ("预占", 0.16),
        ("核销", 0.16),
        ("成本中心", 0.22),
        ("预算科目", 0.22),
        ("预算预警", 0.22),
        ("预警线", 0.18),
        ("超预算", 0.24),
        ("预算不足", 0.24),
    ),
    "knowledge": (
        ("制度", 0.20),
        ("规则", 0.20),
        ("办法", 0.18),
        ("依据", 0.18),
        ("政策", 0.16),
        ("知识库", 0.18),
    ),
}

QUERY_KEYWORDS = (
    "查",
    "查询",
    "查看",
    "列出",
    "统计",
    "汇总",
    "多少",
    "几笔",
    "金额",
    "明细",
)
EXPLAIN_KEYWORDS = ("为什么", "依据", "原因", "怎么处理", "是否可以", "能不能", "按什么规则")
COMPARE_KEYWORDS = ("对比", "比较", "相比", "差异", "变化")
RISK_KEYWORDS = ("风险", "异常", "重复", "超标", "超预算", "逾期", "验真", "巡检")
DRAFT_KEYWORDS = ("生成", "草稿", "起草", "拟一份", "创建", "发起", "准备")
DRAFT_FOLLOW_UP_KEYWORDS = (
    "继续",
    "下一步",
    "核对",
    "补充",
    "补一下",
    "修改",
    "改成",
    "改为",
    "换成",
    "更新",
    "确认",
    "提交",
    "保存",
    "客户是",
    "地点是",
    "金额是",
    "日期是",
    "时间是",
)
EXPENSE_REVIEW_ACTIONS = {
    "save_draft",
    "next_step",
    "edit_review",
    "link_to_existing_draft",
    "create_new_claim_from_documents",
}
OPERATE_KEYWORDS = (
    "直接付款",
    "帮我付款",
    "安排付款",
    "发起付款",
    "直接审批",
    "审批通过",
    "帮我审批",
    "驳回",
    "上线",
    "激活",
    "停用",
    "删除",
)

EXPENSE_TYPE_KEYWORDS = build_expense_type_keyword_map()

EXPENSE_NARRATIVE_KEYWORDS = (
    "报销",
    "报账",
    "招待",
    "招待费",
    "花销",
    "花了",
    "支出",
    "垫付",
    "打车",
    "车费",
    "乘车",
    "乘车费",
    "用车",
    "叫车",
    "车资",
    "的士",
    "的士票",
    "出租车票",
    "餐费",
    "吃饭",
    "用餐",
    "宴请",
    "请客",
    "住宿",
    "发票",
    "票据",
    "差旅",
    "客户现场",
)

AR_CORE_KEYWORDS = ("应收", "回款", "收款", "账龄", "欠款", "未回款")
AP_CORE_KEYWORDS = ("应付", "付款", "请款", "待付", "打款", "未付款")
GENERIC_EXPENSE_PROMPTS = {
    "报销",
    "我要报销",
    "我想报销",
    "帮我报销",
    "我要申请报销",
    "发起报销",
    "提交报销",
}
EXPENSE_APPLICATION_CONTEXT_TYPES = {
    "expense_application",
    "application",
    "pre_approval",
    "preapproval",
}
EXPENSE_APPLICATION_KEYWORDS = (
    "费用申请",
    "申请单",
    "发起申请",
    "提交申请",
    "提出申请",
    "申请出差",
    "申请差旅",
    "前置申请",
    "报销申请",
    "申请报销",
    "差旅费用申请",
    "差旅申请",
    "申请差旅费用",
    "出差申请",
    "会务申请",
    "会议申请",
    "采购申请",
    "培训申请",
    "预算申请",
)
EXPENSE_APPLICATION_REIMBURSEMENT_KEYWORDS = (
    "报销",
    "报销单",
    "报账",
    "票据",
    "发票",
    "行程单",
    "草稿",
    "归集",
    "上传",
    "关联单据",
)
EXPENSE_APPLICATION_COMPLETED_EXPENSE_KEYWORDS = (
    "已经",
    "已",
    "昨天",
    "前天",
    "上周",
    "上月",
    "去年",
    "花了",
    "花销",
    "消费",
    "垫付",
    "支付",
    "付了",
    "买了",
    "采购了",
    "招待了",
    "发生了",
)
EXPENSE_APPLICATION_KNOWLEDGE_QUESTION_KEYWORDS = (
    "制度",
    "政策",
    "标准",
    "规则",
    "规定",
    "流程",
    "口径",
    "依据",
    "上限",
    "额度",
    "补贴",
    "可不可以",
    "能不能",
    "多少",
    "怎么算",
    "如何计算",
)
EXPENSE_APPLICATION_PLANNING_KEYWORDS = (
    "计划",
    "安排",
    "准备",
    "需要",
    "打算",
    "预计",
    "申请",
    "发起",
    "提交",
    "提出",
    "先走",
    "先办",
    "要去",
    "将要",
    "下周",
    "下月",
    "明天",
    "后天",
    "近期",
    "月底",
    "去",
    "到",
    "赴",
    "前往",
    "参加",
)
EXPENSE_APPLICATION_BUSINESS_KEYWORDS = (
    "出差",
    "差旅",
    "客户现场",
    "现场",
    "客户",
    "项目",
    "部署",
    "实施",
    "支撑",
    "支持",
    "协助",
    "拜访",
    "调研",
    "培训",
    "会议",
    "会务",
    "驻场",
    "上线",
    "验收",
    "采购",
    "购置",
    "用款",
    "立项",
)
EXPENSE_APPLICATION_FUTURE_OR_DURATION_PATTERN = re.compile(
    r"明天|后天|下周|下月|近期|月底|预计|计划|安排|准备|将要|"
    r"[0-9]+天|[一二两三四五六七八九十]+天"
)
EXPENSE_APPLICATION_ROUTE_PATTERN = re.compile(
    r"(?:去|到|赴|前往)[^，,。；;！？?!\n]{0,24}"
    r"(?:出差|差旅|客户|现场|项目|部署|实施|支撑|支持|协助|拜访|调研|培训|会议|驻场|上线|验收)"
    r"|(?:出差|差旅)[^，,。；;！？?!\n]{0,24}"
    r"(?:[0-9]+天|[一二两三四五六七八九十]+天|客户|现场|项目|部署|实施|支撑|支持|协助|拜访|调研|培训|会议|驻场|上线|验收)"
)
GENERIC_EXPENSE_APPLICATION_PROMPTS = {
    "申请",
    "费用申请",
    "发起申请",
    "提交申请",
    "提出申请",
    "申请报销",
    "报销申请",
}
EXPENSE_APPLICATION_REQUIRED_SLOT_KEYS = (
    "expense_type",
    "amount",
    "time_range",
    "reason",
)
EXPENSE_APPLICATION_ATTACHMENT_REQUIRED_TYPES = {
    "meeting",
    "office",
    "training",
}
BUDGET_CONTEXT_TYPES = {
    "budget",
    "budget_plan",
    "budget_center",
    "budget_management",
}
BUDGET_KEYWORDS = tuple(keyword for keyword, _weight in SCENARIO_KEYWORDS["budget"])
BUDGET_DRAFT_KEYWORDS = (
    "新建预算",
    "创建预算",
    "编制预算",
    "编辑预算",
    "调整预算",
    "保存预算",
    "预算草稿",
)
BUDGET_OPERATE_KEYWORDS = (
    "发布预算",
    "冻结预算",
    "解冻预算",
    "启用预算",
    "停用预算",
)
BUDGET_REQUIRED_SLOT_KEYS = (
    "budget_period",
    "department",
    "budget_subject",
    "budget_amount",
)
BUDGET_SUBJECT_KEYWORDS = EXPENSE_TYPE_KEYWORDS
BUDGET_SUBJECT_LABEL_BY_CODE = EXPENSE_TYPE_LABEL_BY_CODE
BUDGET_STATUS_KEYWORDS = {
    "编制中": "drafting",
    "草稿": "draft",
    "已发布": "published",
    "发布": "published",
    "已冻结": "frozen",
    "冻结": "frozen",
    "已关闭": "closed",
    "关闭": "closed",
}
BUDGET_CONTROL_ACTION_KEYWORDS = {
    "提醒": "remind",
    "预警": "remind",
    "正常": "allow",
    "允许": "allow",
    "管控": "control",
    "阻断": "block",
    "禁止": "block",
}
MISSING_SLOT_LABELS = {
    "expense_type": "费用类型",
    "amount": "金额",
    "customer_name": "客户单位",
    "vendor_name": "供应商",
    "participants": "参与人员",
    "attachments": "票据附件",
    "time_range": "发生时间",
    "reason": "事由说明",
    "document_id": "单据号",
    "department": "所属部门",
    "budget_period": "预算周期",
    "budget_subject": "预算科目",
    "budget_amount": "预算金额",
    "cost_center": "成本中心",
    "warning_threshold": "预警线",
    "control_action": "控制动作",
}

STATUS_KEYWORDS = {
    "草稿": "draft",
    "待提交": "draft",
    "待补充": "supplement",
    "退回": "returned",
    "已退回": "returned",
    "进行中": "review",
    "审批中": "review",
    "审核中": "review",
    "流转中": "review",
    "已提交": "submitted",
    "逾期": "overdue",
    "待审批": "pending",
    "待审": "pending",
    "已审批": "approved",
    "已通过": "approved",
    "已审核": "approved",
    "归档": "archived",
    "已归档": "archived",
    "入账": "archived",
    "已入账": "paid",
    "已付款": "paid",
    "未付款": "unpaid",
    "未回款": "unreceived",
}

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

PRIVILEGED_ROLE_CODES = {"manager", "finance", "approver", "executive"}
CONTEXTUAL_SCENARIOS = {"expense", "accounts_receivable", "accounts_payable", "budget", "knowledge"}
KNOWLEDGE_INTENTS = {"query", "explain", "compare"}


def looks_like_expense_application_signal(compact_query: str) -> bool:
    if not compact_query:
        return False
    if any(keyword in compact_query for keyword in EXPENSE_APPLICATION_KEYWORDS):
        return True
    if any(keyword in compact_query for keyword in EXPENSE_APPLICATION_REIMBURSEMENT_KEYWORDS):
        return False
    if any(keyword in compact_query for keyword in EXPENSE_APPLICATION_COMPLETED_EXPENSE_KEYWORDS):
        return False
    if any(keyword in compact_query for keyword in EXPENSE_APPLICATION_KNOWLEDGE_QUESTION_KEYWORDS):
        return False

    has_business_signal = any(
        keyword in compact_query for keyword in EXPENSE_APPLICATION_BUSINESS_KEYWORDS
    )
    if not has_business_signal:
        return False

    score = 0
    if any(keyword in compact_query for keyword in EXPENSE_APPLICATION_PLANNING_KEYWORDS):
        score += 1
    if EXPENSE_APPLICATION_FUTURE_OR_DURATION_PATTERN.search(compact_query):
        score += 1
    if EXPENSE_APPLICATION_ROUTE_PATTERN.search(compact_query):
        score += 2

    return score >= 2


@dataclass(slots=True)
class ReferenceCatalog:
    employees: list[str]
    departments: list[str]
    customers: list[str]
    vendors: list[str]
    projects: list[str]


class LlmOntologyEntityHint(BaseModel):
    model_config = ConfigDict(extra="ignore")

    type: str
    value: str
    normalized_value: str | None = None
    role: str = "target"
    confidence: float = Field(default=0.72, ge=0.0, le=1.0)


class LlmOntologyParseResult(BaseModel):
    model_config = ConfigDict(extra="ignore")

    scenario: OntologyScenario = Field(default="unknown")
    intent: OntologyIntent = Field(default="query")
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    clarification_required: bool = False
    clarification_question: str | None = None
    missing_slots: list[str] = Field(default_factory=list)
    ambiguity: list[str] = Field(default_factory=list)
    entity_hints: list[LlmOntologyEntityHint] = Field(default_factory=list)
