from __future__ import annotations

from dataclasses import asdict, dataclass
from datetime import date, datetime
from decimal import Decimal
from typing import Any

from app.services.document_numbering import (
    DOCUMENT_NUMBER_TOKEN_ALPHABET,
    DOCUMENT_NUMBER_TOKEN_LENGTH,
    build_document_number,
)

SIM_EMPLOYEE_PREFIX = "SIM2026"
# 历史模拟数据已用这个命名空间生成 UUID；这里只用于保持幂等，不再作为业务单号。
SIM_CLAIM_ID_NAMESPACE = "SIM-EXP-2026"
SIM_BUDGET_PREFIX = "SIM-BUD-2026"
SIM_TRANSACTION_PREFIX = "SIM-BTX-2026"
SIM_RESERVATION_PREFIX = "SIM-BRS-2026"
SIM_RISK_PREFIX = "SIM-RISK-2026"
SIM_PROJECT_CODE = "SIM-DEMO"
DEFAULT_PASSWORD = "123456"

SUCCESS_STATUSES = {"approved", "pending_payment", "paid", "completed"}
PENDING_STATUSES = {
    "submitted",
    "review",
    "pending_review",
    "manager_review",
    "budget_review",
    "finance_review",
    "approving",
}
BUDGETED_STATUSES = SUCCESS_STATUSES | PENDING_STATUSES


@dataclass(frozen=True, slots=True)
class SimulationConfig:
    target_employees: int = 100
    start_date: date = date(2026, 1, 1)
    end_date: date = date(2026, 6, 2)
    months: int = 6
    seed: int = 20260602


@dataclass(frozen=True, slots=True)
class SimulationSummary:
    mode: str
    current_employee_count: int
    target_employee_count: int
    selected_employee_count: int
    employees_to_create: int
    claims_to_create: int
    claim_items_to_create: int
    budget_allocations_to_create: int
    budget_transactions_to_create: int
    budget_reservations_to_create: int
    risk_observations_to_create: int
    period_start: str
    period_end: str

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


@dataclass(frozen=True, slots=True)
class DepartmentRef:
    id: str
    unit_code: str
    name: str
    cost_center: str
    location: str
    manager_name: str


@dataclass(frozen=True, slots=True)
class EmployeeRef:
    id: str
    employee_no: str
    name: str
    email: str
    grade: str
    position: str
    department: DepartmentRef
    is_new: bool = False


@dataclass(frozen=True, slots=True)
class ClaimItemPlan:
    item_date: date
    item_type: str
    item_reason: str
    item_location: str
    item_amount: Decimal
    invoice_id: str


@dataclass(frozen=True, slots=True)
class ClaimPlan:
    id: str
    claim_no: str
    employee: EmployeeRef
    expense_type: str
    reason: str
    location: str
    amount: Decimal
    invoice_count: int
    occurred_at: datetime
    submitted_at: datetime | None
    status: str
    approval_stage: str | None
    risk_flags: list[dict[str, Any]]
    hermes_risk_flag: bool
    items: list[ClaimItemPlan]

    @property
    def period_key(self) -> str:
        quarter = ((self.occurred_at.month - 1) // 3) + 1
        return f"{self.occurred_at.year}Q{quarter}"

    @property
    def budget_subject_code(self) -> str:
        return "meal" if self.expense_type == "entertainment" else self.expense_type


@dataclass(frozen=True, slots=True)
class AllocationPlan:
    key: tuple[int, str, str, str, str]
    department: DepartmentRef
    subject_code: str
    subject_name: str
    period_key: str
    original_amount: Decimal


DEFAULT_DEPARTMENTS = (
    DepartmentRef("sim-dept-tech", "TECH-DEPT", "技术部", "CC-6100", "北京", "吴磊"),
    DepartmentRef("sim-dept-market", "MARKET-DEPT", "市场部", "CC-4100", "上海", "刘思雨"),
    DepartmentRef("sim-dept-finance", "FINANCE-DEPT", "财务部", "CC-2100", "上海", "张晓晴"),
    DepartmentRef("sim-dept-hr", "HR-DEPT", "人力资源部", "CC-3200", "杭州", "陈硕"),
    DepartmentRef("sim-dept-prod", "PRODUCTION-DEPT", "生产部", "CC-7200", "南京", "梁雨辰"),
    DepartmentRef("sim-dept-office", "PRESIDENT-OFFICE", "总裁办", "CC-1000", "上海", "李文静"),
)

SUBJECT_LABELS = {
    "travel": "差旅",
    "meal": "招待费",
    "office": "办公用品",
    "communication": "通信",
}
SUBJECT_BASE_AMOUNTS = {
    "travel": Decimal("5600.00"),
    "meal": Decimal("1800.00"),
    "office": Decimal("820.00"),
    "communication": Decimal("320.00"),
}
DEPARTMENT_CLAIM_WEIGHTS = {
    "TECH-DEPT": {"travel": 4, "meal": 1, "office": 3, "communication": 2},
    "MARKET-DEPT": {"travel": 5, "meal": 4, "office": 1, "communication": 1},
    "FINANCE-DEPT": {"travel": 2, "meal": 1, "office": 3, "communication": 2},
    "HR-DEPT": {"travel": 2, "meal": 2, "office": 3, "communication": 1},
    "PRODUCTION-DEPT": {"travel": 3, "meal": 1, "office": 4, "communication": 1},
    "PRESIDENT-OFFICE": {"travel": 4, "meal": 3, "office": 2, "communication": 1},
}
DEPARTMENT_EMPLOYEE_WEIGHTS = {
    "TECH-DEPT": 30,
    "MARKET-DEPT": 24,
    "PRODUCTION-DEPT": 20,
    "FINANCE-DEPT": 12,
    "HR-DEPT": 9,
    "PRESIDENT-OFFICE": 5,
}
GRADE_FACTORS = {
    "P3": Decimal("0.82"),
    "P4": Decimal("0.92"),
    "P5": Decimal("1.00"),
    "P6": Decimal("1.15"),
    "P7": Decimal("1.32"),
    "P8": Decimal("1.55"),
}
MONTH_FACTORS = {
    1: Decimal("0.86"),
    2: Decimal("0.72"),
    3: Decimal("1.05"),
    4: Decimal("1.12"),
    5: Decimal("1.22"),
    6: Decimal("1.34"),
}


def build_employee_name(index: int) -> str:
    surnames = ("林", "许", "周", "唐", "沈", "陆", "韩", "钱", "冯", "邹", "顾", "夏")
    names = ("嘉宁", "思远", "雨桐", "景行", "明轩", "若琳", "子涵", "安琪", "奕辰", "诗涵")
    return f"{surnames[index % len(surnames)]}{names[(index * 3) % len(names)]}"


def grade_for_index(index: int) -> str:
    grades = ("P3", "P4", "P4", "P5", "P5", "P6", "P6", "P7", "P8")
    return grades[index % len(grades)]


def position_for_grade(grade: str) -> str:
    return {
        "P3": "专员",
        "P4": "高级专员",
        "P5": "主管",
        "P6": "经理",
        "P7": "高级经理",
        "P8": "部门负责人",
    }.get(grade, "员工")


def claim_reason(expense_type: str, department_name: str, occurred_day: date) -> str:
    labels = {
        "travel": "客户拜访与项目交付差旅",
        "meal": "客户沟通与商务招待",
        "office": "团队办公用品采购",
        "communication": "项目通信与移动办公",
    }
    return f"{department_name}{occurred_day.month}月{labels.get(expense_type, '业务费用')}"


def item_reason(expense_type: str) -> str:
    return {
        "meal": "商务招待餐费",
        "office": "办公用品采购",
        "communication": "通信服务费",
    }.get(expense_type, "业务费用")


def claim_location(default_location: str, claim_index: int) -> str:
    cities = ("上海", "北京", "深圳", "广州", "杭州", "南京", "成都", "武汉")
    return cities[claim_index % len(cities)] or default_location


def risk_type(claim_index: int, expense_type: str) -> tuple[str, str]:
    options = (
        ("amount_outlier", "金额异常"),
        ("budget_pressure", "预算压力偏高"),
        ("missing_material", "材料不完整"),
        ("duplicate_invoice", "疑似重复票据"),
        ("split_billing", "疑似拆分报销"),
    )
    if expense_type == "travel" and claim_index % 5 == 0:
        return "location_mismatch", "地点不一致"
    return options[claim_index % len(options)]


def target_budget_usage(period_key: str, subject_code: str, index: int) -> Decimal:
    sequence = (
        Decimal("0.62"),
        Decimal("0.74"),
        Decimal("0.83"),
        Decimal("0.91"),
        Decimal("1.06"),
    )
    usage = sequence[index % len(sequence)]
    if period_key.endswith("Q2") and subject_code in {"travel", "meal"}:
        usage += Decimal("0.07")
    return min(usage, Decimal("1.12"))


def department_from_row(row: Any | None) -> DepartmentRef:
    if row is None:
        return DEFAULT_DEPARTMENTS[0]
    return DepartmentRef(
        id=row.id,
        unit_code=row.unit_code,
        name=row.name,
        cost_center=row.cost_center or "",
        location=row.location or "上海",
        manager_name=row.manager_name or "",
    )


def updated_at_for_claim_plan(plan: ClaimPlan) -> datetime:
    from datetime import timedelta

    base = plan.submitted_at or plan.occurred_at
    claim_offset = sum(ord(char) for char in str(plan.claim_no or "")[-2:]) % 24
    if plan.status in SUCCESS_STATUSES | {"rejected", "returned"}:
        return base + timedelta(hours=2 + claim_offset)
    return base + timedelta(hours=1)


def build_simulation_reimbursement_no(occurred_at: datetime, sequence: int) -> str:
    return build_document_number(
        "reimbursement",
        timestamp=occurred_at,
        token=simulation_document_token(sequence),
    )


def simulation_document_token(sequence: int) -> str:
    value = max(0, int(sequence))
    base = len(DOCUMENT_NUMBER_TOKEN_ALPHABET)
    chars: list[str] = []
    for _ in range(DOCUMENT_NUMBER_TOKEN_LENGTH):
        chars.append(DOCUMENT_NUMBER_TOKEN_ALPHABET[value % base])
        value //= base
    return "".join(reversed(chars))
