from __future__ import annotations

from collections.abc import Callable
from datetime import date, datetime

from app.models.employee import Employee
from app.models.employee_change_log import EmployeeChangeLog
from app.models.role import Role
from app.schemas.employee import (
    EmployeeHistoryRead,
    EmployeeOrganizationRead,
    EmployeeRead,
)


def serialize_employee(
    employee: Employee,
    *,
    sorted_roles: list[Role],
    sorted_change_logs: list[EmployeeChangeLog],
    format_date: Callable[[date | None], str | None],
    format_datetime: Callable[[datetime | None], str | None],
    format_history_datetime: Callable[[datetime | None], str],
    role_permission_map: dict[str, list[str]],
    status_tone_map: dict[str, str],
    max_change_logs: int,
) -> EmployeeRead:
    organization = employee.organization_unit
    role_labels = [role.name for role in sorted_roles]
    role_codes = [role.role_code for role in sorted_roles]

    history = [
        EmployeeHistoryRead(
            action=item.action,
            owner=item.owner,
            time=format_history_datetime(item.occurred_at),
            occurredAt=format_history_datetime(item.occurred_at),
        )
        for item in sorted_change_logs[:max_change_logs]
    ]

    return EmployeeRead(
        id=employee.id,
        avatar=(employee.name or "?")[:1],
        name=employee.name,
        employeeNo=employee.employee_no,
        department=organization.name if organization else "",
        position=employee.position,
        grade=employee.grade,
        manager=employee.manager.name if employee.manager else "CEO",
        managerEmployeeNo=employee.manager.employee_no if employee.manager else None,
        financeOwner=employee.finance_owner_name or "",
        roles=role_labels,
        roleCodes=role_codes,
        status=employee.employment_status,
        statusTone=status_tone_map.get(employee.employment_status, "neutral"),
        gender=employee.gender,
        age=calculate_age(employee.birth_date),
        birthDate=format_date(employee.birth_date),
        email=employee.email,
        phone=employee.phone,
        joinDate=format_date(employee.join_date),
        location=employee.location,
        costCenter=employee.cost_center,
        bankName=employee.bank_name,
        bankAccountNo=employee.bank_account_no,
        bankAccountName=employee.bank_account_name,
        updatedAt=format_datetime(employee.updated_at or employee.created_at),
        lastSync=format_datetime(employee.last_sync_at),
        syncState=employee.sync_state,
        spotlight=employee.spotlight,
        permissions=collect_permissions(role_codes, role_permission_map),
        history=history,
        organization=(
            EmployeeOrganizationRead(
                id=organization.id,
                code=organization.unit_code,
                name=organization.name,
                unitType=organization.unit_type,
                costCenter=organization.cost_center,
                location=organization.location,
                managerName=organization.manager_name,
            )
            if organization
            else None
        ),
    )


def collect_permissions(
    role_codes: list[str],
    role_permission_map: dict[str, list[str]],
) -> list[str]:
    permissions: list[str] = []
    seen: set[str] = set()

    for role_code in role_codes:
        for permission in role_permission_map.get(role_code, []):
            if permission in seen:
                continue
            permissions.append(permission)
            seen.add(permission)

    return permissions


def format_history_datetime(
    value: datetime | None,
    *,
    to_display_datetime: Callable[[datetime], datetime],
) -> str:
    if value is None:
        return ""
    local = to_display_datetime(value)
    return (
        f"{local.year}年{local.month}月{local.day}日"
        f"{local.hour}时{local.minute}分"
    )


def calculate_age(birth_date: date | None) -> int | None:
    if birth_date is None:
        return None

    today = date.today()
    age = today.year - birth_date.year
    if (today.month, today.day) < (birth_date.month, birth_date.day):
        age -= 1
    return age
