from __future__ import annotations

import calendar
from datetime import date
from typing import Any

ADMIN_KEYWORDS = {
    "admin",
    "administrator",
    "root",
    "system",
    "sysadmin",
    "superadmin",
}
ADMIN_CN_KEYWORDS = ("管理员", "系统")
APPLICATION_EXPENSE_TYPES = {
    "application",
    "expense_application",
    "travel_application",
    "trip_application",
    "preapproval",
}
APPLICATION_CLAIM_PREFIXES = ("AP-", "APP-", "TA-")
RECENT_VISIBLE_CLAIM_START = 401
RECENT_VISIBLE_CLAIM_END = 424


def is_admin_identity(*values: Any) -> bool:
    for value in values:
        text = str(value or "").strip()
        lowered = text.lower()
        if not text:
            continue
        if lowered in ADMIN_KEYWORDS:
            return True
        if any(token in lowered for token in ADMIN_KEYWORDS):
            return True
        if any(token in text for token in ADMIN_CN_KEYWORDS):
            return True
    return False


def is_admin_employee_like(employee: Any) -> bool:
    return is_admin_identity(
        getattr(employee, "employee_no", None),
        getattr(employee, "name", None),
        getattr(employee, "email", None),
    )


def is_application_claim(claim: Any) -> bool:
    expense_type = str(getattr(claim, "expense_type", "") or "").strip().lower()
    claim_no = str(getattr(claim, "claim_no", "") or "").strip().upper()
    if expense_type in APPLICATION_EXPENSE_TYPES:
        return True
    return claim_no.startswith(APPLICATION_CLAIM_PREFIXES)


def is_finance_reimbursement_claim(claim: Any) -> bool:
    if is_application_claim(claim):
        return False
    return not is_admin_identity(
        getattr(claim, "employee_name", None),
        getattr(claim, "employee_id", None),
    )


def recent_visible_claim_day(
    months: list[date],
    *,
    employee_index: int,
    claim_index: int,
    period_end: date,
) -> date | None:
    if not months or not (RECENT_VISIBLE_CLAIM_START <= claim_index <= RECENT_VISIBLE_CLAIM_END):
        return None
    month = months[-1]
    _, max_day = calendar.monthrange(month.year, month.month)
    if month.year == period_end.year and month.month == period_end.month:
        max_day = min(max_day, period_end.day)
    day = min(2, max_day)
    return month.replace(day=1 + ((employee_index + claim_index) % day))


def simulation_claim_day(
    rng: Any,
    months: list[date],
    *,
    employee_index: int,
    local_index: int,
    claim_index: int,
    period_end: date,
) -> date:
    visible_day = recent_visible_claim_day(
        months,
        employee_index=employee_index,
        claim_index=claim_index,
        period_end=period_end,
    )
    if visible_day is not None:
        return visible_day
    distribution_months = complete_distribution_months(months, period_end)
    month = distribution_months[(employee_index + local_index * 2) % len(distribution_months)]
    _, max_day = calendar.monthrange(month.year, month.month)
    if month.year == period_end.year and month.month == period_end.month:
        max_day = min(max_day, period_end.day)
    day = 1 + ((employee_index * 7 + local_index * 11 + rng.randint(0, 5)) % max_day)
    return month.replace(day=day)


def simulation_claim_count(employee: Any, index: int) -> int:
    base = 3 + (1 if index % 3 == 0 else 0)
    department_code = str(getattr(getattr(employee, "department", None), "unit_code", "") or "")
    grade = str(getattr(employee, "grade", "") or "")
    if department_code in {"MARKET-DEPT", "TECH-DEPT"}:
        base += 1
    elif department_code in {"PRODUCTION-DEPT", "PRESIDENT-OFFICE"}:
        base += 1
    if grade in {"P7", "P8"}:
        base += 1
    return max(3, min(base, 6))


def complete_distribution_months(months: list[date], period_end: date) -> list[date]:
    complete_months: list[date] = []
    for month in months:
        _, max_day = calendar.monthrange(month.year, month.month)
        if month.year == period_end.year and month.month == period_end.month and period_end.day < 10:
            continue
        complete_months.append(month)
    return complete_months or months


def next_simulation_number(prefix: str, used_numbers: set[str], cursor: int) -> tuple[str, int]:
    while True:
        number = f"{prefix}-{cursor:04d}"
        cursor += 1
        if number not in used_numbers:
            used_numbers.add(number)
            return number, cursor


def simulation_month_starts(config: Any) -> list[date]:
    current = config.start_date.replace(day=1)
    months: list[date] = []
    for _ in range(max(1, config.months)):
        if current > config.end_date:
            break
        months.append(current)
        year = current.year + (1 if current.month == 12 else 0)
        month = 1 if current.month == 12 else current.month + 1
        current = date(year, month, 1)
    return months or [config.start_date.replace(day=1)]


def simulation_period_end(config: Any) -> date:
    last_month = simulation_month_starts(config)[-1]
    _, max_day = calendar.monthrange(last_month.year, last_month.month)
    return min(last_month.replace(day=max_day), config.end_date)
