from __future__ import annotations

import re
from datetime import datetime

from fastapi import HTTPException
from sqlalchemy import and_, delete, exists, func, or_, select
from sqlalchemy.orm import Session

from app.models.org import (
    Department,
    Employee,
    OrgEmployeeBinding,
    OrgManagerBinding,
    Permission,
    Role,
    RolePermission,
    User,
    UserRole,
)
from app.schemas.system_permissions import (
    EmployeeOption,
    MenuPermissionNode,
    OrgEmployeeNode,
    OrgEmployeeBind,
    OrgNodeCreate,
    OrgNodeRead,
    OrgNodeUpdate,
    PermissionRead,
    RoleCreate,
    RoleRead,
    RoleUpdate,
    SystemUserCreate,
    SystemUserRead,
)
from app.services.auth import hash_password


MENU_PERMISSION_TREE = [
    ("MENU_DASHBOARD", "经营驾驶舱", []),
    (
        "MENU_MASTER_DATA",
        "基础资料",
        [
            ("MENU_SUPPLIER_MANAGEMENT", "供应商名录", []),
            ("MENU_MATERIAL_MANAGEMENT", "原材料名录", []),
            ("MENU_SALES_PLANNING", "客户名录/销售订单", []),
            ("MENU_PRODUCT_MANAGEMENT", "产品管理", []),
            ("MENU_BOM_MANAGEMENT", "BOM管理", []),
            ("MENU_ROUTE_MANAGEMENT", "工艺路线管理", []),
            ("MENU_EMPLOYEE_MANAGEMENT", "人员管理", []),
        ],
    ),
    (
        "MENU_PROCUREMENT",
        "采购与库存",
        [
            ("MENU_PURCHASE_ORDER", "采购订单", []),
            ("MENU_PURCHASE_RECEIPT", "到货入库", []),
            ("MENU_QUALITY_INSPECTION", "质量检验", []),
            ("MENU_INVENTORY_LEDGER", "嘉恒仓库", []),
        ],
    ),
    (
        "MENU_PRODUCTION",
        "生产执行",
        [
            ("MENU_WORK_ORDER", "工单台账", []),
            ("MENU_OPERATION_REPORT", "工序报工", []),
            ("MENU_EQUIPMENT_MANAGEMENT", "设备管理", []),
        ],
    ),
    ("MENU_DELIVERY_MANAGEMENT", "发货概览", []),
    ("MENU_FINANCE_REPORT", "财务分析", []),
    (
        "MENU_SYSTEM_MANAGEMENT",
        "系统管理",
        [
            ("MENU_SYSTEM_PERMISSION", "系统权限管理", []),
            ("MENU_SYSTEM_EXTENSION", "系统拓展", []),
        ],
    ),
]

ORG_ROOT_CODE = "ORG_ROOT"
ORG_ROOT_NAME = "总公司"
VALID_CHILD_NODE_TYPES = {
    "COMPANY": {"BRANCH": "总公司下只能新增分公司"},
    "BRANCH": {"DEPARTMENT": "分公司下只能新增部门"},
    "DEPARTMENT": {"GROUP": "部门下只能新增小组"},
    "GROUP": {},
}
ORG_NODE_TYPE_LABELS = {
    "COMPANY": "总公司",
    "BRANCH": "分公司",
    "DEPARTMENT": "部门",
    "GROUP": "小组",
}


def _now() -> datetime:
    return datetime.now()


def _normalize_phone(value: str | None) -> str:
    return re.sub(r"\s+", "", str(value or "").strip())


def _employee_name_sort_key(employee: Employee) -> tuple[str, int]:
    surname_order = {
        "王": "wang",
        "李": "li",
        "张": "zhang",
        "刘": "liu",
        "陈": "chen",
        "杨": "yang",
        "赵": "zhao",
        "黄": "huang",
        "周": "zhou",
        "吴": "wu",
        "徐": "xu",
        "孙": "sun",
        "胡": "hu",
        "朱": "zhu",
        "高": "gao",
        "林": "lin",
        "何": "he",
        "郭": "guo",
        "马": "ma",
        "罗": "luo",
    }
    name = employee.employee_name or ""
    return (f"{surname_order.get(name[:1], name[:1])}{name[1:]}", employee.id)


def _menu_node(raw: tuple[str, str, list]) -> MenuPermissionNode:
    code, name, children = raw
    return MenuPermissionNode(
        permission_code=code,
        permission_name=name,
        children=[_menu_node(child) for child in children],
    )


def list_menu_permission_tree() -> list[MenuPermissionNode]:
    return [_menu_node(item) for item in MENU_PERMISSION_TREE]


def list_permissions(db: Session) -> list[PermissionRead]:
    rows = db.scalars(select(Permission).order_by(Permission.module_code, Permission.id)).all()
    return [
        PermissionRead(
            permission_id=row.id,
            permission_code=row.permission_code,
            permission_name=row.permission_name,
            module_code=row.module_code,
            action_code=row.action_code,
            status=row.status,
        )
        for row in rows
    ]


def _employee_role_map(db: Session, employee_ids: list[int]) -> tuple[dict[int, str], dict[int, list[str]]]:
    if not employee_ids:
        return {}, {}
    rows = db.execute(
        select(User.employee_id, User.username, Role.role_name)
        .select_from(User)
        .outerjoin(UserRole, UserRole.user_id == User.id)
        .outerjoin(Role, Role.id == UserRole.role_id)
        .where(User.employee_id.in_(employee_ids))
        .order_by(User.employee_id, Role.id)
    ).all()
    username_map: dict[int, str] = {}
    role_map: dict[int, list[str]] = {}
    for employee_id, username, role_name in rows:
        if employee_id is None:
            continue
        username_map.setdefault(employee_id, username)
        if role_name:
            role_map.setdefault(employee_id, [])
            if role_name not in role_map[employee_id]:
                role_map[employee_id].append(role_name)
    return username_map, role_map


def _ensure_org_root(db: Session) -> Department:
    root = db.scalar(select(Department).where(Department.dept_code == ORG_ROOT_CODE))
    if root:
        return root

    now = _now()
    root = Department(
        dept_code=ORG_ROOT_CODE,
        dept_name=ORG_ROOT_NAME,
        parent_id=None,
        org_node_type="COMPANY",
        dept_type="ADMIN",
        manager_name=None,
        manager_employee_id=None,
        status="ACTIVE",
        sort_no=0,
        remark="系统权限管理组织脑图根节点",
        created_at=now,
        updated_at=now,
    )
    db.add(root)
    db.commit()
    db.refresh(root)
    return root


def _managed_department_ids(root: Department, departments: list[Department]) -> set[int]:
    children_by_parent: dict[int | None, list[Department]] = {}
    for dept in departments:
        children_by_parent.setdefault(dept.parent_id, []).append(dept)

    result: set[int] = set()

    def walk(node_id: int) -> None:
        result.add(node_id)
        for child in children_by_parent.get(node_id, []):
            walk(child.id)

    walk(root.id)
    return result


def _bound_employees_by_dept(db: Session, dept_ids: set[int]) -> dict[int, list[OrgEmployeeNode]]:
    if not dept_ids:
        return {}
    rows = db.execute(
        select(OrgEmployeeBinding, Employee)
        .join(Employee, Employee.id == OrgEmployeeBinding.employee_id)
        .where(
            OrgEmployeeBinding.dept_id.in_(dept_ids),
            OrgEmployeeBinding.status == "ACTIVE",
            Employee.status == "ACTIVE",
        )
        .order_by(OrgEmployeeBinding.dept_id, OrgEmployeeBinding.sort_no, OrgEmployeeBinding.id)
    ).all()
    employees = [employee for _, employee in rows]
    username_map, role_map = _employee_role_map(db, [employee.id for employee in employees])
    employees_by_dept: dict[int, list[OrgEmployeeNode]] = {}
    for binding, employee in rows:
        employees_by_dept.setdefault(binding.dept_id, []).append(
            OrgEmployeeNode(
                employee_id=employee.id,
                employee_name=employee.employee_name,
                mobile=employee.mobile,
                job_title=employee.job_title,
                username=username_map.get(employee.id),
                role_names=role_map.get(employee.id, []),
                status=employee.status,
            )
        )
    return employees_by_dept


def _active_employees_for_ids(db: Session, employee_ids: list[int]) -> list[Employee]:
    unique_ids = list(dict.fromkeys(employee_ids))
    if not unique_ids:
        return []
    rows = db.scalars(select(Employee).where(Employee.id.in_(unique_ids), Employee.status == "ACTIVE")).all()
    employee_map = {employee.id: employee for employee in rows}
    missing = [employee_id for employee_id in unique_ids if employee_id not in employee_map]
    if missing:
        raise HTTPException(status_code=400, detail=f"主管不存在或已停用：{missing}")
    return [employee_map[employee_id] for employee_id in unique_ids]


def _manager_payload_ids(payload: OrgNodeCreate | OrgNodeUpdate) -> list[int]:
    if payload.manager_employee_ids:
        return list(dict.fromkeys(payload.manager_employee_ids))
    return [payload.manager_employee_id] if payload.manager_employee_id else []


def _replace_org_managers(db: Session, dept: Department, manager_employee_ids: list[int]) -> None:
    managers = _active_employees_for_ids(db, manager_employee_ids)
    db.execute(delete(OrgManagerBinding).where(OrgManagerBinding.dept_id == dept.id))
    now = _now()
    for index, manager in enumerate(managers, start=1):
        db.add(
            OrgManagerBinding(
                dept_id=dept.id,
                employee_id=manager.id,
                sort_no=index,
                status="ACTIVE",
                created_at=now,
                updated_at=now,
            )
        )
    dept.manager_employee_id = managers[0].id if managers else None
    dept.manager_name = "、".join(manager.employee_name for manager in managers) if managers else None


def _manager_bindings_by_dept(
    db: Session,
    dept_ids: set[int],
    departments: list[Department],
) -> dict[int, tuple[list[int], list[str]]]:
    if not dept_ids:
        return {}
    rows = db.execute(
        select(OrgManagerBinding, Employee)
        .join(Employee, Employee.id == OrgManagerBinding.employee_id)
        .where(
            OrgManagerBinding.dept_id.in_(dept_ids),
            OrgManagerBinding.status == "ACTIVE",
            Employee.status == "ACTIVE",
        )
        .order_by(OrgManagerBinding.dept_id, OrgManagerBinding.sort_no, OrgManagerBinding.id)
    ).all()
    result: dict[int, tuple[list[int], list[str]]] = {}
    for binding, employee in rows:
        manager_ids, manager_names = result.setdefault(binding.dept_id, ([], []))
        manager_ids.append(employee.id)
        manager_names.append(employee.employee_name)

    legacy_ids = [dept.manager_employee_id for dept in departments if dept.id in dept_ids and dept.manager_employee_id]
    legacy_employees = (
        {employee.id: employee for employee in db.scalars(select(Employee).where(Employee.id.in_(legacy_ids))).all()}
        if legacy_ids
        else {}
    )
    for dept in departments:
        if dept.id not in dept_ids or dept.id in result:
            continue
        if dept.manager_employee_id:
            employee = legacy_employees.get(dept.manager_employee_id)
            result[dept.id] = (
                [dept.manager_employee_id],
                [employee.employee_name if employee else dept.manager_name or "未命名主管"],
            )
        elif dept.manager_name:
            result[dept.id] = ([], [dept.manager_name])
    return result


def build_org_tree(db: Session) -> list[OrgNodeRead]:
    root = _ensure_org_root(db)
    departments = db.scalars(
        select(Department).order_by(Department.parent_id.is_not(None), Department.sort_no, Department.id)
    ).all()
    managed_ids = _managed_department_ids(root, departments)
    departments = [dept for dept in departments if dept.id in managed_ids]
    employees_by_dept = _bound_employees_by_dept(db, managed_ids)
    managers_by_dept = _manager_bindings_by_dept(db, managed_ids, departments)

    nodes: dict[int, OrgNodeRead] = {
        dept.id: OrgNodeRead(
            node_id=dept.id,
            node_type=getattr(dept, "org_node_type", None) or "DEPARTMENT",
            node_label=dept.dept_name,
            parent_id=dept.parent_id,
            dept_code=dept.dept_code,
            dept_type=dept.dept_type,
            manager_employee_id=(managers_by_dept.get(dept.id, ([], []))[0] or [None])[0],
            manager_name="、".join(managers_by_dept.get(dept.id, ([], []))[1]) or None,
            manager_employee_ids=managers_by_dept.get(dept.id, ([], []))[0],
            manager_names=managers_by_dept.get(dept.id, ([], []))[1],
            status=dept.status,
            sort_no=dept.sort_no,
            remark=dept.remark,
            employees=employees_by_dept.get(dept.id, []),
            children=[],
        )
        for dept in departments
    }
    roots: list[OrgNodeRead] = []
    for dept in departments:
        node = nodes[dept.id]
        if dept.parent_id and dept.parent_id in nodes:
            nodes[dept.parent_id].children.append(node)
        elif dept.id == root.id:
            roots.append(node)
    return roots


def _flatten_org_nodes(nodes: list[OrgNodeRead]) -> list[OrgNodeRead]:
    result: list[OrgNodeRead] = []
    for node in nodes:
        result.append(node)
        result.extend(_flatten_org_nodes(node.children))
    return result


def _next_department_code(db: Session, node_type: str) -> str:
    prefix = {"COMPANY": "COMP", "BRANCH": "BR", "DEPARTMENT": "DEPT", "GROUP": "GRP"}.get(node_type, "DEPT")
    count = db.scalar(select(func.count(Department.id)).where(Department.dept_code.like(f"{prefix}-%"))) or 0
    return f"{prefix}-{int(count) + 1:03d}"


def _manager_name(db: Session, employee_id: int | None) -> str | None:
    if not employee_id:
        return None
    employee = db.get(Employee, employee_id)
    if not employee or employee.status != "ACTIVE":
        raise HTTPException(status_code=400, detail="负责人不存在或已停用")
    return employee.employee_name


def create_org_node(db: Session, payload: OrgNodeCreate) -> OrgNodeRead:
    if not payload.parent_id:
        if payload.node_type != "COMPANY":
            raise HTTPException(status_code=400, detail="请选择上级组织节点")
    parent = db.get(Department, payload.parent_id) if payload.parent_id else None
    if payload.parent_id and not parent:
        raise HTTPException(status_code=404, detail="上级组织节点不存在")
    if parent:
        parent_type = getattr(parent, "org_node_type", None) or "DEPARTMENT"
        allowed = VALID_CHILD_NODE_TYPES.get(parent_type, {})
        if payload.node_type not in allowed:
            message = next(iter(allowed.values()), f"{ORG_NODE_TYPE_LABELS.get(parent_type, '当前节点')}下不能新增组织节点")
            raise HTTPException(status_code=400, detail=message)
    dept_code = payload.dept_code or _next_department_code(db, payload.node_type)
    if db.scalar(select(Department).where(Department.dept_code == dept_code)):
        raise HTTPException(status_code=400, detail="组织编码已存在")
    now = _now()
    row = Department(
        dept_code=dept_code,
        dept_name=payload.node_label,
        parent_id=payload.parent_id,
        org_node_type=payload.node_type,
        dept_type=payload.dept_type,
        manager_name=None,
        manager_employee_id=None,
        status=payload.status,
        sort_no=payload.sort_no,
        remark=payload.remark,
        created_at=now,
        updated_at=now,
    )
    db.add(row)
    db.flush()
    _replace_org_managers(db, row, _manager_payload_ids(payload))
    db.add(row)
    db.commit()
    return next(node for node in _flatten_org_nodes(build_org_tree(db)) if node.node_id == row.id)


def update_org_node(db: Session, node_id: int, payload: OrgNodeUpdate) -> OrgNodeRead:
    row = db.get(Department, node_id)
    if not row:
        raise HTTPException(status_code=404, detail="组织节点不存在")
    row.dept_name = payload.node_label
    row.dept_type = payload.dept_type
    _replace_org_managers(db, row, _manager_payload_ids(payload))
    row.sort_no = payload.sort_no
    row.remark = payload.remark
    row.status = payload.status
    row.updated_at = _now()
    db.add(row)
    db.commit()
    return next(node for node in _flatten_org_nodes(build_org_tree(db)) if node.node_id == row.id)


def delete_org_node(db: Session, node_id: int) -> None:
    row = db.get(Department, node_id)
    if not row:
        raise HTTPException(status_code=404, detail="组织节点不存在")
    child_count = db.scalar(select(func.count(Department.id)).where(Department.parent_id == node_id)) or 0
    if int(child_count) > 0:
        raise HTTPException(status_code=400, detail="该节点存在下级节点，请先从底层开始删除")
    employee_count = (
        db.scalar(
            select(func.count(OrgEmployeeBinding.id)).where(
                OrgEmployeeBinding.dept_id == node_id,
                OrgEmployeeBinding.status == "ACTIVE",
            )
        )
        or 0
    )
    if int(employee_count) > 0:
        raise HTTPException(status_code=400, detail="该节点存在人员节点，请先移除人员")
    if row.dept_code == ORG_ROOT_CODE:
        raise HTTPException(status_code=400, detail="总公司根节点不能删除")
    db.execute(delete(OrgManagerBinding).where(OrgManagerBinding.dept_id == node_id))
    db.delete(row)
    db.commit()


def bind_org_employee(db: Session, node_id: int, payload: OrgEmployeeBind) -> OrgNodeRead:
    node = db.get(Department, node_id)
    if not node:
        raise HTTPException(status_code=404, detail="组织节点不存在")
    node_type = getattr(node, "org_node_type", None) or "DEPARTMENT"
    if node_type not in {"DEPARTMENT", "GROUP"}:
        raise HTTPException(status_code=400, detail="只有部门或小组节点可以新增人员")
    employee = db.get(Employee, payload.employee_id)
    if not employee or employee.status != "ACTIVE":
        raise HTTPException(status_code=400, detail="人员不存在或已停用")

    existing = db.scalar(
        select(OrgEmployeeBinding).where(
            OrgEmployeeBinding.dept_id == node_id,
            OrgEmployeeBinding.employee_id == payload.employee_id,
        )
    )
    now = _now()
    if existing:
        existing.status = "ACTIVE"
        existing.sort_no = payload.sort_no
        existing.updated_at = now
        db.add(existing)
    else:
        db.add(
            OrgEmployeeBinding(
                dept_id=node_id,
                employee_id=payload.employee_id,
                sort_no=payload.sort_no,
                status="ACTIVE",
                created_at=now,
                updated_at=now,
            )
        )
    db.commit()
    return next(node for node in _flatten_org_nodes(build_org_tree(db)) if node.node_id == node_id)


def unbind_org_employee(db: Session, node_id: int, employee_id: int) -> OrgNodeRead:
    binding = db.scalar(
        select(OrgEmployeeBinding).where(
            OrgEmployeeBinding.dept_id == node_id,
            OrgEmployeeBinding.employee_id == employee_id,
            OrgEmployeeBinding.status == "ACTIVE",
        )
    )
    if not binding:
        raise HTTPException(status_code=404, detail="人员节点不存在")
    db.delete(binding)
    db.commit()
    return next(node for node in _flatten_org_nodes(build_org_tree(db)) if node.node_id == node_id)


def list_roles(db: Session) -> list[RoleRead]:
    roles = db.scalars(select(Role).order_by(Role.id)).all()
    if not roles:
        return []
    role_ids = [role.id for role in roles]
    permission_rows = db.execute(
        select(RolePermission.role_id, Permission.permission_code)
        .join(Permission, Permission.id == RolePermission.permission_id)
        .where(RolePermission.role_id.in_(role_ids))
        .order_by(RolePermission.role_id, Permission.permission_code)
    ).all()
    user_counts = dict(
        db.execute(
            select(UserRole.role_id, func.count(UserRole.user_id))
            .where(UserRole.role_id.in_(role_ids))
            .group_by(UserRole.role_id)
        ).all()
    )
    permission_map: dict[int, list[str]] = {}
    for role_id, code in permission_rows:
        permission_map.setdefault(role_id, []).append(code)
    return [
        RoleRead(
            role_id=role.id,
            role_code=role.role_code,
            role_name=role.role_name,
            role_scope=role.role_scope,
            status=role.status,
            remark=role.remark,
            permission_codes=permission_map.get(role.id, []),
            user_count=int(user_counts.get(role.id, 0)),
            created_at=role.created_at,
            updated_at=role.updated_at,
        )
        for role in roles
    ]


def _replace_role_permissions(db: Session, role_id: int, permission_codes: list[str]) -> None:
    permissions = _permissions_for_codes(db, permission_codes)
    db.execute(delete(RolePermission).where(RolePermission.role_id == role_id))
    now = _now()
    for permission in permissions:
        db.add(RolePermission(role_id=role_id, permission_id=permission.id, created_at=now))


def _permissions_for_codes(db: Session, permission_codes: list[str]) -> list[Permission]:
    unique_codes = list(dict.fromkeys(permission_codes))
    permissions = (
        db.scalars(select(Permission).where(Permission.permission_code.in_(unique_codes))).all()
        if unique_codes
        else []
    )
    missing = sorted(set(unique_codes) - {row.permission_code for row in permissions})
    if missing:
        raise HTTPException(status_code=400, detail=f"权限不存在：{'、'.join(missing)}")
    return permissions


def create_role(db: Session, payload: RoleCreate) -> RoleRead:
    if db.scalar(select(Role).where(Role.role_code == payload.role_code)):
        raise HTTPException(status_code=400, detail="角色编码已存在")
    _permissions_for_codes(db, payload.permission_codes)
    now = _now()
    row = Role(
        role_code=payload.role_code,
        role_name=payload.role_name,
        role_scope=payload.role_scope,
        status=payload.status,
        remark=payload.remark,
        created_at=now,
        updated_at=now,
    )
    db.add(row)
    db.flush()
    _replace_role_permissions(db, row.id, payload.permission_codes)
    db.commit()
    return next(role for role in list_roles(db) if role.role_id == row.id)


def update_role(db: Session, role_id: int, payload: RoleUpdate) -> RoleRead:
    row = db.get(Role, role_id)
    if not row:
        raise HTTPException(status_code=404, detail="角色不存在")
    _permissions_for_codes(db, payload.permission_codes)
    row.role_name = payload.role_name
    row.role_scope = payload.role_scope
    row.status = payload.status
    row.remark = payload.remark
    row.updated_at = _now()
    db.add(row)
    _replace_role_permissions(db, row.id, payload.permission_codes)
    db.commit()
    return next(role for role in list_roles(db) if role.role_id == row.id)


def _system_user_read(db: Session, user: User) -> SystemUserRead:
    employee = db.get(Employee, user.employee_id)
    department = db.get(Department, user.dept_id)
    role_rows = db.execute(
        select(Role.id, Role.role_name)
        .join(UserRole, UserRole.role_id == Role.id)
        .where(UserRole.user_id == user.id)
        .order_by(Role.id)
    ).all()
    return SystemUserRead(
        user_id=user.id,
        username=user.username,
        employee_id=user.employee_id,
        employee_name=employee.employee_name if employee else user.nickname or user.username,
        dept_id=user.dept_id,
        dept_name=department.dept_name if department else None,
        mobile=employee.mobile if employee else None,
        nickname=user.nickname,
        is_super_admin=bool(user.is_super_admin),
        status=user.status,
        role_ids=[row[0] for row in role_rows],
        role_names=[row[1] for row in role_rows],
        last_login_at=user.last_login_at,
        created_at=user.created_at,
        updated_at=user.updated_at,
    )


def list_system_users(db: Session) -> list[SystemUserRead]:
    rows = db.scalars(select(User).order_by(User.status, User.id)).all()
    return [_system_user_read(db, row) for row in rows]


def _permission_by_code(db: Session, permission_code: str) -> Permission:
    permission = db.scalar(
        select(Permission).where(
            Permission.permission_code == permission_code,
            Permission.status == "ACTIVE",
        )
    )
    if not permission:
        raise HTTPException(status_code=400, detail=f"权限码不存在或已停用：{permission_code}")
    return permission


def _employee_role_permission_maps(
    db: Session,
    employee_ids: list[int],
) -> tuple[dict[int, User], dict[int, list[str]], dict[int, list[str]]]:
    if not employee_ids:
        return {}, {}, {}
    users = db.scalars(
        select(User)
        .where(
            User.employee_id.in_(employee_ids),
            User.status == "ACTIVE",
        )
        .order_by(User.id)
    ).all()
    user_by_employee_id = {user.employee_id: user for user in users}
    if not users:
        return {}, {}, {}
    rows = db.execute(
        select(User.employee_id, Role.role_name, Permission.permission_code)
        .join(UserRole, UserRole.user_id == User.id)
        .join(Role, Role.id == UserRole.role_id)
        .outerjoin(RolePermission, RolePermission.role_id == Role.id)
        .outerjoin(
            Permission,
            and_(Permission.id == RolePermission.permission_id, Permission.status == "ACTIVE"),
        )
        .where(
            User.id.in_([user.id for user in users]),
            Role.status == "ACTIVE",
        )
        .order_by(User.employee_id, Role.id, Permission.permission_code)
    ).all()
    role_map: dict[int, list[str]] = {}
    permission_map: dict[int, list[str]] = {}
    for employee_id, role_name, permission_code in rows:
        if role_name and role_name not in role_map.setdefault(employee_id, []):
            role_map[employee_id].append(role_name)
        if permission_code and permission_code not in permission_map.setdefault(employee_id, []):
            permission_map[employee_id].append(permission_code)
    return user_by_employee_id, role_map, permission_map


def employee_has_permission(db: Session, employee_id: int, permission_code: str) -> bool:
    permission = _permission_by_code(db, permission_code)
    user = db.scalar(
        select(User).where(
            User.employee_id == employee_id,
            User.status == "ACTIVE",
        )
    )
    if not user:
        return False
    employee = db.get(Employee, employee_id)
    if not employee or employee.status != "ACTIVE":
        return False
    if bool(user.is_super_admin):
        return True
    return bool(
        db.scalar(
            select(
                exists()
                .where(UserRole.user_id == user.id)
                .where(UserRole.role_id == Role.id)
                .where(Role.status == "ACTIVE")
                .where(RolePermission.role_id == Role.id)
                .where(RolePermission.permission_id == permission.id)
            )
        )
    )


def ensure_employee_has_permission(
    db: Session,
    employee_id: int | None,
    permission_code: str,
    field_label: str,
    *,
    required: bool = False,
) -> None:
    if employee_id is None:
        if required:
            raise HTTPException(status_code=400, detail=f"{field_label}必填")
        return
    if employee_id <= 0:
        raise HTTPException(status_code=400, detail=f"{field_label}不存在或已停用")
    permission = _permission_by_code(db, permission_code)
    employee = db.get(Employee, employee_id)
    if not employee or employee.status != "ACTIVE":
        raise HTTPException(status_code=400, detail=f"{field_label}不存在或已停用")
    if not employee_has_permission(db, employee_id, permission_code):
        raise HTTPException(
            status_code=400,
            detail=f"{field_label}必须选择有“{permission.permission_name}”权限的人员",
        )


def list_employee_options(
    db: Session,
    permission_code: str | None = None,
    limit: int = 500,
) -> list[EmployeeOption]:
    permission = _permission_by_code(db, permission_code) if permission_code else None
    stmt = select(Employee).where(Employee.status == "ACTIVE")
    if permission:
        permitted_user_exists = (
            exists()
            .where(User.employee_id == Employee.id)
            .where(User.status == "ACTIVE")
            .where(
                or_(
                    User.is_super_admin == 1,
                    exists()
                    .where(UserRole.user_id == User.id)
                    .where(UserRole.role_id == Role.id)
                    .where(Role.status == "ACTIVE")
                    .where(RolePermission.role_id == Role.id)
                    .where(RolePermission.permission_id == permission.id),
                )
            )
        )
        stmt = stmt.where(permitted_user_exists)
    employees = sorted(
        db.scalars(stmt.order_by(Employee.id)).all(),
        key=_employee_name_sort_key,
    )[:limit]
    dept_map = {row.id: row.dept_name for row in db.scalars(select(Department)).all()}
    employee_ids = [employee.id for employee in employees]
    user_map, role_map, permission_map = _employee_role_permission_maps(db, employee_ids)
    return [
        EmployeeOption(
            employee_id=employee.id,
            employee_name=employee.employee_name,
            dept_id=employee.dept_id,
            dept_name=dept_map.get(employee.dept_id),
            mobile=employee.mobile,
            job_title=employee.job_title,
            username=user_map[employee.id].username if employee.id in user_map else None,
            role_names=role_map.get(employee.id, []),
            permission_codes=permission_map.get(employee.id, []),
            has_system_user=employee.id in user_map,
        )
        for employee in employees
    ]


def _replace_user_roles(db: Session, user_id: int, role_ids: list[int]) -> None:
    roles = _roles_for_ids(db, role_ids)
    db.execute(delete(UserRole).where(UserRole.user_id == user_id))
    now = _now()
    for role in roles:
        db.add(UserRole(user_id=user_id, role_id=role.id, created_at=now))


def _roles_for_ids(db: Session, role_ids: list[int]) -> list[Role]:
    unique_role_ids = list(dict.fromkeys(role_ids))
    roles = db.scalars(select(Role).where(Role.id.in_(unique_role_ids), Role.status == "ACTIVE")).all()
    missing = sorted(set(unique_role_ids) - {row.id for row in roles})
    if missing:
        raise HTTPException(status_code=400, detail=f"角色不存在或已停用：{missing}")
    return roles


def create_system_user(db: Session, payload: SystemUserCreate) -> SystemUserRead:
    employee = db.get(Employee, payload.employee_id)
    if not employee or employee.status != "ACTIVE":
        raise HTTPException(status_code=400, detail="人员不存在或已停用")
    username = _normalize_phone(employee.mobile)
    if not username:
        raise HTTPException(status_code=400, detail="人员没有电话号码，不能创建系统账号")
    if db.scalar(select(User).where(User.employee_id == employee.id)):
        raise HTTPException(status_code=400, detail="该人员已经有系统账号")
    if db.scalar(select(User).where(User.username == username)):
        raise HTTPException(status_code=400, detail="该电话号码已经作为系统账号存在")
    _roles_for_ids(db, payload.role_ids)
    now = _now()
    user = User(
        username=username,
        password_hash=hash_password(payload.password),
        employee_id=employee.id,
        dept_id=employee.dept_id,
        nickname=employee.employee_name,
        email=None,
        is_super_admin=0,
        last_login_at=None,
        status=payload.status,
        created_at=now,
        updated_at=now,
    )
    db.add(user)
    db.flush()
    _replace_user_roles(db, user.id, payload.role_ids)
    db.commit()
    return _system_user_read(db, user)


def update_system_user_roles(db: Session, user_id: int, role_ids: list[int]) -> SystemUserRead:
    user = db.get(User, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="系统用户不存在")
    _replace_user_roles(db, user.id, role_ids)
    user.updated_at = _now()
    db.add(user)
    db.commit()
    return _system_user_read(db, user)


def update_system_user_status(db: Session, user_id: int, status: str, current_user_id: int) -> SystemUserRead:
    user = db.get(User, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="系统用户不存在")
    if user.id == current_user_id and status != "ACTIVE":
        raise HTTPException(status_code=400, detail="不能停用当前登录账号")
    user.status = status
    user.updated_at = _now()
    db.add(user)
    db.commit()
    return _system_user_read(db, user)


def reset_system_user_password(db: Session, user_id: int, password: str) -> SystemUserRead:
    user = db.get(User, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="系统用户不存在")
    user.password_hash = hash_password(password)
    user.updated_at = _now()
    db.add(user)
    db.commit()
    return _system_user_read(db, user)
