from __future__ import annotations

import threading
from collections import Counter
from datetime import UTC, datetime
from typing import Any

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.core.config import get_settings
from app.core.logging import get_logger
from app.core.security import hash_password
from app.db.schema_ownership import create_legacy_schema
from app.db.session import get_session_factory
from app.models.employee import Employee
from app.models.employee_change_log import EmployeeChangeLog
from app.models.organization import OrganizationUnit
from app.models.role import Role
from app.repositories.employee import EmployeeRepository
from app.schemas.employee import (
    EmployeeCreate,
    EmployeeImportResultRead,
    EmployeeMetaRead,
    EmployeeOrganizationRead,
    EmployeeRead,
    EmployeeRoleOptionRead,
    EmployeeStatusSummaryRead,
    EmployeeUpdate,
)
from app.services.employee_bank_info import apply_default_bank_info
from app.services.employee_directory_maintenance import EmployeeDirectoryMaintenance
from app.services.employee_import import EmployeeImportCoordinator
from app.services.employee_schema import ensure_employee_schema
from app.services.employee_seed import (
    CANONICAL_DEPARTMENT_CODES,
    EMPLOYEE_DEFINITIONS,
    EMPLOYEE_PROFILE_REPAIRS,
    ORGANIZATION_DEFINITIONS,
    ROLE_DEFINITIONS,
    ROLE_DISPLAY_ORDER,
    ROLE_PERMISSION_MAP,
    normalize_organization_unit_code,
)
from app.services.employee_serialization import serialize_employee
from app.services.employee_spreadsheet import build_import_template_bytes
from app.services.employee_time import (
    format_date,
    format_datetime,
    format_history_datetime,
    normalize_optional_text,
    parse_date,
    parse_datetime,
)
from app.services.tenant_registry import (
    DEFAULT_TENANT_ID,
    TenantRegistryService,
    required_tenant_id,
)

logger = get_logger("app.services.employee")
DEFAULT_EMPLOYEE_PASSWORD = "123456"
MAX_EMPLOYEE_CHANGE_LOGS = 5

STATUS_TONE_MAP = {
    "在职": "success",
    "试用中": "warning",
    "停用": "neutral",
}

STATUS_ORDER = ["全部员工", "在职", "试用中", "停用"]
SEEDED_EMPLOYEE_DEFINITIONS = EMPLOYEE_DEFINITIONS[:30]
EXTRA_SEED_EMPLOYEE_NOS = {item["employee_no"] for item in EMPLOYEE_DEFINITIONS[30:]}


def prepare_employee_directory() -> None:
    settings = get_settings()
    if not settings.setup_completed:
        logger.info("Employee directory bootstrap skipped because setup is incomplete")
        return

    session_factory = get_session_factory()
    with session_factory() as db:
        service = EmployeeService(db, tenant_id=DEFAULT_TENANT_ID)
        service.ensure_directory_ready()
        service.apply_profile_repairs()


class EmployeeService:
    _directory_ready_lock = threading.Lock()
    _directory_ready_keys: set[tuple[str, int, str]] = set()

    def __init__(self, db: Session, *, tenant_id: str) -> None:
        self.db = db
        self.tenant_id = required_tenant_id(tenant_id)
        self.repository = EmployeeRepository(db, tenant_id=self.tenant_id)
        self.directory_maintenance = EmployeeDirectoryMaintenance(db, self.repository)

    def _bind_cache_key(self, db: Session) -> tuple[str, int, str]:
        bind = db.get_bind()
        return (
            bind.url.render_as_string(hide_password=True),
            id(bind.pool),
            self.tenant_id,
        )

    def ensure_directory_ready(self) -> None:
        cache_key = self._bind_cache_key(self.db)
        if cache_key in self._directory_ready_keys:
            self.directory_maintenance.normalize_after_cache_hit()
            return

        with self._directory_ready_lock:
            if cache_key in self._directory_ready_keys:
                self.directory_maintenance.normalize_after_cache_hit()
                return

            self._ensure_directory_ready_uncached()
            self._directory_ready_keys.add(cache_key)

    def _ensure_directory_ready_uncached(self) -> None:
        try:
            create_legacy_schema(self.db.get_bind())
            ensure_employee_schema(self.db)
            tenant_registry = TenantRegistryService(self.db)
            tenant_registry.ensure_builtin()
            tenant_registry.require_active(self.tenant_id)
            self._prune_extra_seed_employees()
            self._seed_roles()
            self._seed_organization_units()
            self._seed_employees()
            self.directory_maintenance.normalize_legacy_departments()
            self._backfill_employee_bank_info()
            self._ensure_tenant_memberships()
            self.db.commit()
        except Exception:
            self.db.rollback()
            logger.exception("Failed to prepare employee directory")
            raise

    def list_employees(
        self, status: str | None = None, keyword: str | None = None
    ) -> list[EmployeeRead]:
        self.ensure_directory_ready()
        employees = self.repository.list(status=status, keyword=keyword)
        logger.info(
            "Listed employees (count=%d, status=%s, keyword=%s)", len(employees), status, keyword
        )
        return [self._serialize_employee(item) for item in employees]

    def get_employee(self, employee_id: str) -> EmployeeRead | None:
        self.ensure_directory_ready()
        employee = self.repository.get(employee_id)
        if employee is None:
            logger.warning("Employee not found id=%s", employee_id)
            return None

        logger.info("Fetched employee id=%s name=%s", employee_id, employee.name)
        return self._serialize_employee(employee)

    def get_employee_meta(self) -> EmployeeMetaRead:
        self.ensure_directory_ready()
        employees = self.repository.list()
        status_counter = Counter(item.employment_status for item in employees)

        status_summary = [
            EmployeeStatusSummaryRead(
                id=status,
                label=status,
                count=len(employees) if status == "全部员工" else status_counter.get(status, 0),
            )
            for status in STATUS_ORDER
        ]

        visible_role_codes = {item["role_code"] for item in ROLE_DEFINITIONS}
        role_options = [
            EmployeeRoleOptionRead(
                id=role.role_code,
                code=role.role_code,
                label=role.name,
                desc=role.description,
                permissions=list(ROLE_PERMISSION_MAP.get(role.role_code, [])),
            )
            for role in self._sorted_roles(self.repository.list_roles())
            if role.role_code in visible_role_codes
        ]

        canonical_department_codes = set(CANONICAL_DEPARTMENT_CODES)
        organization_options = [
            EmployeeOrganizationRead(
                id=unit.id,
                code=unit.unit_code,
                name=unit.name,
                unitType=unit.unit_type,
                costCenter=unit.cost_center,
                location=unit.location,
                managerName=unit.manager_name,
            )
            for unit in sorted(
                (
                    unit
                    for unit in self.repository.list_organization_units()
                    if unit.unit_code in canonical_department_codes
                ),
                key=lambda item: item.name,
            )
        ]

        return EmployeeMetaRead(
            totalEmployees=len(employees),
            statusSummary=status_summary,
            roleOptions=role_options,
            organizationOptions=organization_options,
        )

    def create_employee(self, payload: EmployeeCreate) -> EmployeeRead:
        self.ensure_directory_ready()

        if self.repository.get_by_employee_no(payload.employee_no):
            raise ValueError(f"员工编号 {payload.employee_no} 已存在")

        if self.repository.get_by_email(str(payload.email)):
            raise ValueError(f"邮箱 {payload.email} 已存在")

        employee = Employee(
            tenant_id=self.tenant_id,
            employee_no=payload.employee_no,
            name=payload.name,
            email=str(payload.email),
            gender=payload.gender,
            birth_date=payload.parsed_birth_date(),
            phone=payload.phone,
            join_date=payload.parsed_join_date(),
            location=payload.location,
            position=payload.position,
            grade=payload.grade,
            cost_center=payload.cost_center,
            finance_owner_name=payload.finance_owner_name,
            bank_name=normalize_optional_text(payload.bank_name),
            bank_account_no=normalize_optional_text(payload.bank_account_no),
            bank_account_name=normalize_optional_text(payload.bank_account_name),
            employment_status=payload.employment_status,
            sync_state=payload.sync_state,
            spotlight=payload.spotlight,
            password_hash=hash_password(DEFAULT_EMPLOYEE_PASSWORD),
            last_sync_at=datetime.now(UTC),
        )
        apply_default_bank_info(employee)

        if payload.organization_unit_code:
            organization_code = normalize_organization_unit_code(payload.organization_unit_code)
            employee.organization_unit = self.repository.get_organization_by_code(organization_code)

        if payload.manager_employee_no:
            employee.manager = self.repository.get_by_employee_no(payload.manager_employee_no)

        roles = [
            role
            for code in payload.role_codes
            if (role := self.repository.get_role_by_code(code)) is not None
        ]
        employee.roles = self._sorted_roles(roles)

        self.db.add(employee)
        self.db.flush()
        TenantRegistryService(self.db).ensure_employee_membership(employee)
        self.db.commit()
        self.db.refresh(employee)
        created = employee
        logger.info(
            "Created employee id=%s no=%s name=%s", created.id, created.employee_no, created.name
        )

        hydrated = self.repository.get(created.id)
        return self._serialize_employee(hydrated or created)

    def update_employee(self, employee_id: str, payload: EmployeeUpdate) -> EmployeeRead:
        self.ensure_directory_ready()

        employee = self.repository.get(employee_id)
        if employee is None:
            raise LookupError("Employee not found")

        changed_fields: list[str] = []
        password_changed = False

        if "name" in payload.model_fields_set and payload.name is not None:
            name = payload.name.strip()
            if not name:
                raise ValueError("员工姓名不能为空")
            if name != employee.name:
                employee.name = name
                changed_fields.append("姓名")

        if "gender" in payload.model_fields_set:
            gender = normalize_optional_text(payload.gender)
            if gender != employee.gender:
                employee.gender = gender
                changed_fields.append("性别")

        if "birth_date" in payload.model_fields_set:
            birth_date = payload.parsed_birth_date() if payload.birth_date else None
            if birth_date != employee.birth_date:
                employee.birth_date = birth_date
                changed_fields.append("出生日期")

        if "phone" in payload.model_fields_set:
            phone = normalize_optional_text(payload.phone)
            if phone != employee.phone:
                employee.phone = phone
                changed_fields.append("手机号")

        if "email" in payload.model_fields_set and payload.email is not None:
            email = str(payload.email)
            existing = self.repository.get_by_email(email)
            if existing is not None and existing.id != employee.id:
                raise ValueError(f"邮箱 {email} 已存在")
            if email != employee.email:
                employee.email = email
                changed_fields.append("邮箱")

        if "join_date" in payload.model_fields_set:
            join_date = payload.parsed_join_date() if payload.join_date else None
            if join_date != employee.join_date:
                employee.join_date = join_date
                changed_fields.append("入职日期")

        if "location" in payload.model_fields_set:
            location = normalize_optional_text(payload.location)
            if location != employee.location:
                employee.location = location
                changed_fields.append("办公地点")

        if "position" in payload.model_fields_set and payload.position is not None:
            position = payload.position.strip()
            if not position:
                raise ValueError("岗位不能为空")
            if position != employee.position:
                employee.position = position
                changed_fields.append("岗位")

        if "grade" in payload.model_fields_set and payload.grade is not None:
            grade = payload.grade.strip()
            if not grade:
                raise ValueError("职级不能为空")
            if grade != employee.grade:
                employee.grade = grade
                changed_fields.append("职级")

        if "cost_center" in payload.model_fields_set:
            cost_center = normalize_optional_text(payload.cost_center)
            if cost_center != employee.cost_center:
                employee.cost_center = cost_center
                changed_fields.append("成本中心")

        if "finance_owner_name" in payload.model_fields_set:
            finance_owner_name = normalize_optional_text(payload.finance_owner_name)
            if finance_owner_name != employee.finance_owner_name:
                employee.finance_owner_name = finance_owner_name
                changed_fields.append("财务归口")

        if "bank_account_name" in payload.model_fields_set:
            bank_account_name = normalize_optional_text(payload.bank_account_name)
            if bank_account_name != employee.bank_account_name:
                employee.bank_account_name = bank_account_name
                changed_fields.append("银行户名")

        if "bank_name" in payload.model_fields_set:
            bank_name = normalize_optional_text(payload.bank_name)
            if bank_name != employee.bank_name:
                employee.bank_name = bank_name
                changed_fields.append("开户行")

        if "bank_account_no" in payload.model_fields_set:
            bank_account_no = normalize_optional_text(payload.bank_account_no)
            if bank_account_no != employee.bank_account_no:
                employee.bank_account_no = bank_account_no
                changed_fields.append("银行账号")

        if "organization_unit_code" in payload.model_fields_set:
            organization_code = normalize_organization_unit_code(
                normalize_optional_text(payload.organization_unit_code)
            )
            current_code = (
                employee.organization_unit.unit_code if employee.organization_unit else None
            )
            if organization_code != current_code:
                if organization_code:
                    organization = self.repository.get_organization_by_code(organization_code)
                    if organization is None:
                        raise ValueError(f"部门编码 {organization_code} 不存在")
                    employee.organization_unit = organization
                else:
                    employee.organization_unit = None
                changed_fields.append("所属部门")

        if "manager_employee_no" in payload.model_fields_set:
            manager_employee_no = normalize_optional_text(payload.manager_employee_no)
            current_manager_no = employee.manager.employee_no if employee.manager else None

            if manager_employee_no:
                if manager_employee_no == employee.employee_no:
                    raise ValueError("直属上级不能是员工本人")

                manager = self.repository.get_by_employee_no(manager_employee_no)
                if manager is None:
                    raise ValueError(f"直属上级工号 {manager_employee_no} 不存在")

                if manager_employee_no != current_manager_no:
                    employee.manager = manager
                    changed_fields.append("直属上级")
            elif current_manager_no is not None:
                employee.manager = None
                changed_fields.append("直属上级")

        role_changed = False
        sorted_roles: list[Role] = []

        if "role_codes" in payload.model_fields_set and payload.role_codes is not None:
            requested_codes = list(dict.fromkeys(payload.role_codes))
            roles: list[Role] = []
            invalid_codes: list[str] = []
            for code in requested_codes:
                role = self.repository.get_role_by_code(code)
                if role is None:
                    invalid_codes.append(code)
                    continue
                roles.append(role)

            if invalid_codes:
                raise ValueError(f"角色不存在：{'、'.join(invalid_codes)}")

            sorted_roles = self._sorted_roles(roles)
            next_role_codes = [role.role_code for role in sorted_roles]
            current_role_codes = [
                role.role_code for role in self._sorted_roles(list(employee.roles))
            ]
            if next_role_codes != current_role_codes:
                employee.roles = sorted_roles
                role_changed = True

        if "password" in payload.model_fields_set and payload.password:
            password = payload.password.strip()
            if len(password) < 5:
                raise ValueError("员工密码至少需要 5 位")
            employee.password_hash = hash_password(password)
            password_changed = True

        if not changed_fields and not password_changed and not role_changed:
            return self._serialize_employee(employee)

        now = datetime.now(UTC)
        employee.last_sync_at = now
        employee.sync_state = "已同步"

        if changed_fields:
            self._append_change_log(
                employee,
                action=f"更新员工信息（{'、'.join(changed_fields)}）",
                occurred_at=now,
            )

        if role_changed:
            role_labels = "、".join(role.name for role in sorted_roles)
            self._append_change_log(
                employee,
                action=f"更新系统角色（{role_labels}）",
                occurred_at=now,
            )

        if password_changed:
            self._append_change_log(employee, action="重置员工登录密码", occurred_at=now)

        hydrated = self._save_employee_and_reload(employee)
        logger.info(
            "Updated employee id=%s fields=%s role_changed=%s",
            employee.id,
            ",".join(changed_fields),
            role_changed,
        )
        return self._serialize_employee(hydrated)

    def disable_employee(self, employee_id: str) -> EmployeeRead:
        self.ensure_directory_ready()

        employee = self.repository.get(employee_id)
        if employee is None:
            raise LookupError("Employee not found")

        if employee.employment_status == "停用":
            return self._serialize_employee(employee)

        now = datetime.now(UTC)
        employee.employment_status = "停用"
        for membership in employee.tenant_memberships:
            membership.status = "inactive"
        employee.sync_state = "已同步"
        employee.last_sync_at = now
        employee.spotlight = False
        self._append_change_log(employee, action="停用员工账号", occurred_at=now)

        hydrated = self._save_employee_and_reload(employee)
        logger.info("Disabled employee id=%s no=%s", employee.id, employee.employee_no)
        return self._serialize_employee(hydrated)

    def enable_employee(self, employee_id: str) -> EmployeeRead:
        self.ensure_directory_ready()

        employee = self.repository.get(employee_id)
        if employee is None:
            raise LookupError("Employee not found")

        if employee.employment_status != "停用":
            return self._serialize_employee(employee)

        now = datetime.now(UTC)
        employee.employment_status = "在职"
        TenantRegistryService(self.db).ensure_employee_membership(employee)
        employee.sync_state = "已同步"
        employee.last_sync_at = now
        self._append_change_log(employee, action="启用员工账号", occurred_at=now)

        hydrated = self._save_employee_and_reload(employee)
        logger.info("Enabled employee id=%s no=%s", employee.id, employee.employee_no)
        return self._serialize_employee(hydrated)

    def build_import_template(self) -> bytes:
        self.ensure_directory_ready()
        return build_import_template_bytes()

    def export_employees(self, status: str | None = None, keyword: str | None = None) -> bytes:
        self.ensure_directory_ready()
        return self._import_coordinator().export_employees(status=status, keyword=keyword)

    def import_employees(
        self, content: bytes, actor: str = "系统管理员"
    ) -> EmployeeImportResultRead:
        self.ensure_directory_ready()
        try:
            result = self._import_coordinator().import_employees(content, actor=actor)
            if not result.success:
                return result
            self._ensure_tenant_memberships()
            self.db.commit()
            return result
        except Exception:
            self.db.rollback()
            raise

    def _import_coordinator(self) -> EmployeeImportCoordinator:
        return EmployeeImportCoordinator(
            self.db,
            self.repository,
            sorted_roles=self._sorted_roles,
            append_change_log=self._append_change_log,
            format_date=format_date,
            format_datetime=format_datetime,
            default_password=DEFAULT_EMPLOYEE_PASSWORD,
        )

    def _seed_roles(self) -> None:
        existing_by_code = {role.role_code: role for role in self.repository.list_roles()}
        legacy_auditor = existing_by_code.get("auditor")
        if legacy_auditor is not None and "budget_monitor" not in existing_by_code:
            legacy_auditor.role_code = "budget_monitor"
            existing_by_code["budget_monitor"] = legacy_auditor
            existing_by_code.pop("auditor", None)

        for definition in ROLE_DEFINITIONS:
            role = existing_by_code.get(definition["role_code"])
            if role is None:
                role = Role(
                    role_code=definition["role_code"],
                    name=definition["name"],
                    description=definition["description"],
                )
                self.db.add(role)
                existing_by_code[role.role_code] = role
            else:
                role.name = definition["name"]
                role.description = definition["description"]

        self.db.flush()

    def _seed_organization_units(self) -> None:
        existing_by_code = {
            unit.unit_code: unit for unit in self.repository.list_organization_units()
        }

        for definition in ORGANIZATION_DEFINITIONS:
            organization = existing_by_code.get(definition["unit_code"])
            if organization is None:
                organization = OrganizationUnit(
                    tenant_id=self.tenant_id,
                    unit_code=definition["unit_code"],
                    name=definition["name"],
                    unit_type=definition["unit_type"],
                    cost_center=definition.get("cost_center"),
                    location=definition.get("location"),
                    manager_name=definition.get("manager_name"),
                )
                self.db.add(organization)
                existing_by_code[organization.unit_code] = organization
            else:
                organization.name = definition["name"]
                organization.unit_type = definition["unit_type"]
                organization.cost_center = definition.get("cost_center")
                organization.location = definition.get("location")
                organization.manager_name = definition.get("manager_name")

        self.db.flush()

        for definition in ORGANIZATION_DEFINITIONS:
            parent_code = definition.get("parent_code")
            if not parent_code:
                continue

            organization = existing_by_code[definition["unit_code"]]
            parent = existing_by_code.get(parent_code)
            if parent is not None and organization.parent_id != parent.id:
                organization.parent = parent

        self.db.flush()

    def _seed_employees(self) -> None:
        employees_by_no = {employee.employee_no: employee for employee in self.repository.list()}
        roles_by_code = {role.role_code: role for role in self.repository.list_roles()}
        organizations_by_code = {
            unit.unit_code: unit for unit in self.repository.list_organization_units()
        }

        for definition in SEEDED_EMPLOYEE_DEFINITIONS:
            employee_no = definition["employee_no"]
            if employee_no in employees_by_no:
                continue

            employee = Employee(
                tenant_id=self.tenant_id,
                employee_no=employee_no,
                name=definition["name"],
                email=definition["email"],
                gender=definition.get("gender"),
                birth_date=parse_date(definition.get("birth_date")),
                phone=definition.get("phone"),
                join_date=parse_date(definition.get("join_date")),
                location=definition.get("location"),
                position=definition.get("position", "员工"),
                grade=definition.get("grade", "P3"),
                cost_center=definition.get("cost_center"),
                finance_owner_name=definition.get("finance_owner_name"),
                bank_name=definition.get("bank_name"),
                bank_account_no=definition.get("bank_account_no"),
                bank_account_name=definition.get("bank_account_name"),
                employment_status=definition.get("employment_status", "在职"),
                sync_state=definition.get("sync_state", "已同步"),
                spotlight=bool(definition.get("spotlight")),
                last_sync_at=parse_datetime(definition.get("last_sync_at")),
                updated_at=parse_datetime(definition.get("updated_at")),
            )
            self.db.add(employee)
            employees_by_no[employee_no] = employee

        self.db.flush()

        for definition in SEEDED_EMPLOYEE_DEFINITIONS:
            employee = employees_by_no[definition["employee_no"]]
            organization_code = definition.get("organization_unit_code")
            manager_employee_no = definition.get("manager_employee_no")

            if employee.organization_unit_id is None and organization_code:
                employee.organization_unit = organizations_by_code.get(organization_code)

            if employee.manager_id is None and manager_employee_no:
                employee.manager = employees_by_no.get(manager_employee_no)

            if not employee.password_hash:
                employee.password_hash = hash_password(DEFAULT_EMPLOYEE_PASSWORD)

            apply_default_bank_info(employee)

            if not employee.roles:
                employee.roles = self._sorted_roles(
                    [
                        roles_by_code[role_code]
                        for role_code in definition.get("role_codes", [])
                        if role_code in roles_by_code
                    ]
                )

            self._seed_employee_history(employee, definition)

        self.db.flush()

    def _ensure_tenant_memberships(self) -> None:
        registry = TenantRegistryService(self.db)
        for employee in self.repository.list():
            registry.ensure_employee_membership(employee)

    def apply_profile_repairs(self) -> None:
        """Apply one-off demo profile repairs. Intended for startup/bootstrap only."""
        try:
            self._repair_employee_profiles()
            self._trim_all_employee_change_logs()
            self.db.commit()
        except Exception:
            self.db.rollback()
            logger.exception("Failed to apply employee profile repairs")
            raise

    def _repair_employee_profiles(self) -> None:
        if not EMPLOYEE_PROFILE_REPAIRS:
            return

        employees = self.repository.list()
        employees_by_email = {
            employee.email.lower(): employee for employee in employees if employee.email
        }
        employees_by_no = {
            employee.employee_no: employee for employee in employees if employee.employee_no
        }
        roles_by_code = {role.role_code: role for role in self.repository.list_roles()}
        organizations_by_code = {
            unit.unit_code: unit for unit in self.repository.list_organization_units()
        }

        for definition in EMPLOYEE_PROFILE_REPAIRS:
            email = str(definition.get("email") or "").strip().lower()
            employee_no = str(definition.get("employee_no") or "").strip()
            employee = employees_by_email.get(email) or employees_by_no.get(employee_no)
            if employee is None:
                continue

            for field_name in (
                "position",
                "grade",
                "location",
                "cost_center",
                "finance_owner_name",
                "bank_name",
                "bank_account_no",
                "bank_account_name",
                "employment_status",
                "sync_state",
            ):
                value = definition.get(field_name)
                if value:
                    setattr(employee, field_name, value)

            organization_code = definition.get("organization_unit_code")
            if organization_code:
                employee.organization_unit = organizations_by_code.get(organization_code)

            manager_employee_no = definition.get("manager_employee_no")
            if manager_employee_no:
                employee.manager = employees_by_no.get(manager_employee_no)

            if not employee.password_hash:
                employee.password_hash = hash_password(DEFAULT_EMPLOYEE_PASSWORD)

            apply_default_bank_info(employee)

            role_codes = [
                item for item in definition.get("role_codes", []) if item in roles_by_code
            ]
            if role_codes:
                merged_roles = {role.role_code: role for role in employee.roles}
                for role_code in role_codes:
                    merged_roles[role_code] = roles_by_code[role_code]
                employee.roles = self._sorted_roles(list(merged_roles.values()))

        self.db.flush()

    def _prune_extra_seed_employees(self) -> None:
        if not EXTRA_SEED_EMPLOYEE_NOS:
            return

        for employee_no in EXTRA_SEED_EMPLOYEE_NOS:
            employee = self.repository.get_by_employee_no(employee_no)
            if employee is not None:
                self.db.delete(employee)

    def _backfill_employee_bank_info(self) -> None:
        for employee in self.repository.list():
            apply_default_bank_info(employee)
        self.db.flush()

    def _seed_employee_history(self, employee: Employee, definition: dict[str, Any]) -> None:
        existing_keys = {
            (item.action, item.owner, format_datetime(item.occurred_at))
            for item in employee.change_logs
        }

        history_items = list(definition.get("history", []))
        if not history_items:
            history_items = [
                {
                    "action": "初始化员工档案",
                    "owner": "系统初始化任务",
                    "occurred_at": definition.get("updated_at") or definition.get("last_sync_at"),
                }
            ]

        for history in history_items:
            occurred_at = parse_datetime(history.get("occurred_at"))
            if occurred_at is None:
                continue

            identity = (
                history["action"],
                history["owner"],
                format_datetime(occurred_at),
            )
            if identity in existing_keys:
                continue

            self.db.add(
                EmployeeChangeLog(
                    employee=employee,
                    action=history["action"],
                    owner=history["owner"],
                    occurred_at=occurred_at,
                )
            )
            existing_keys.add(identity)

    def _save_employee_and_reload(self, employee: Employee) -> Employee:
        saved = self.repository.save(employee)
        self._trim_employee_change_logs(saved.id)
        self.db.commit()
        return self.repository.get(saved.id) or saved

    def _append_change_log(
        self,
        employee: Employee,
        action: str,
        owner: str = "系统管理员",
        occurred_at: datetime | None = None,
    ) -> None:
        self.db.add(
            EmployeeChangeLog(
                employee=employee,
                action=action,
                owner=owner,
                occurred_at=occurred_at or datetime.now(UTC),
            )
        )

    def _trim_all_employee_change_logs(self) -> None:
        for employee in self.repository.list():
            self._trim_employee_change_logs(employee.id)

    def _sorted_change_logs(self, employee: Employee) -> list[EmployeeChangeLog]:
        return sorted(employee.change_logs, key=lambda item: item.occurred_at, reverse=True)

    def _trim_employee_change_logs(self, employee_id: str) -> None:
        stmt = (
            select(EmployeeChangeLog)
            .where(EmployeeChangeLog.employee_id == employee_id)
            .order_by(EmployeeChangeLog.occurred_at.desc())
        )
        logs = list(self.db.execute(stmt).scalars().all())
        if len(logs) <= MAX_EMPLOYEE_CHANGE_LOGS:
            return

        for stale in logs[MAX_EMPLOYEE_CHANGE_LOGS:]:
            self.db.delete(stale)

    def _serialize_employee(self, employee: Employee) -> EmployeeRead:
        return serialize_employee(
            employee,
            sorted_roles=self._sorted_roles(list(employee.roles)),
            sorted_change_logs=self._sorted_change_logs(employee),
            format_date=format_date,
            format_datetime=format_datetime,
            format_history_datetime=format_history_datetime,
            role_permission_map=ROLE_PERMISSION_MAP,
            status_tone_map=STATUS_TONE_MAP,
            max_change_logs=MAX_EMPLOYEE_CHANGE_LOGS,
        )

    def _sorted_roles(self, roles: list[Role]) -> list[Role]:
        return sorted(
            roles, key=lambda item: (ROLE_DISPLAY_ORDER.get(item.role_code, 999), item.name)
        )
