"""后台工单管理路由。"""
from __future__ import annotations

from datetime import datetime, timezone
from typing import Any

from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile
from pydantic import BaseModel, Field, field_validator
from sqlalchemy import func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.deps import require_role
from app.database import get_db
from app.models.ticket import (
    Ticket,
    TicketDepartment,
    TicketEventType,
    TicketMessage,
    TicketPriority,
    TicketReplyTemplate,
    TicketSenderType,
    TicketStatus,
)
from app.models.user import User, UserRole, UserStatus
from app.routers.tickets import (
    _add_ticket_event,
    _add_message,
    _claim_message_attachments,
    _dt,
    _display_user,
    _load_ticket_events,
    _message_count,
    _message_counts_batch,
    _notify_admins_for_ticket,
    _reject_embedded_images,
    _safe_upload_name,
    _save_ticket_image_upload,
    _serialize_department,
    _serialize_instance,
    _serialize_message,
    _serialize_messages_batch,
    _serialize_satisfaction,
    ticket_image_file,
    ticket_image_thumbnail,
    ticket_image_thumbnail_by_token,
)
from app.schemas.common import ApiResponse

router = APIRouter()
_admin = require_role("admin", "super_admin")


class DepartmentCreate(BaseModel):
    code: str | None = Field(default=None, max_length=64)
    name: str = Field(min_length=1, max_length=80)
    description: str | None = Field(default=None, max_length=255)
    is_enabled: bool = True
    sort_order: int = 0
    min_member_level: int | None = Field(default=None, ge=0, le=99)
    auto_reply_enabled: bool = False
    auto_reply_content: str | None = Field(default=None, max_length=4000)


class DepartmentUpdate(BaseModel):
    code: str | None = Field(default=None, max_length=64)
    name: str | None = Field(default=None, min_length=1, max_length=80)
    description: str | None = Field(default=None, max_length=255)
    is_enabled: bool | None = None
    sort_order: int | None = None
    min_member_level: int | None = Field(default=None, ge=0, le=99)
    auto_reply_enabled: bool | None = None
    auto_reply_content: str | None = Field(default=None, max_length=4000)


class AdminTicketReply(BaseModel):
    content: str = Field(min_length=1, max_length=8000)
    attachments: list[dict[str, Any]] | None = None
    is_internal: bool = False

    @field_validator("content")
    @classmethod
    def validate_content(cls, value: str) -> str:
        return _reject_embedded_images(value)


class ReplyTemplateCreate(BaseModel):
    title: str = Field(min_length=1, max_length=120)
    content: str = Field(min_length=1, max_length=8000)
    department_id: int | None = None
    is_enabled: bool = True
    sort_order: int = 0

    @field_validator("title")
    @classmethod
    def validate_title(cls, value: str) -> str:
        value = value.strip()
        if not value:
            raise ValueError("回复模板标题不能为空")
        return value

    @field_validator("content")
    @classmethod
    def validate_content(cls, value: str) -> str:
        return _reject_embedded_images(value)


class ReplyTemplateUpdate(BaseModel):
    title: str | None = Field(default=None, min_length=1, max_length=120)
    content: str | None = Field(default=None, min_length=1, max_length=8000)
    department_id: int | None = None
    is_enabled: bool | None = None
    sort_order: int | None = None

    @field_validator("title")
    @classmethod
    def validate_title(cls, value: str | None) -> str | None:
        if value is None:
            return None
        value = value.strip()
        if not value:
            raise ValueError("回复模板标题不能为空")
        return value

    @field_validator("content")
    @classmethod
    def validate_content(cls, value: str | None) -> str | None:
        return _reject_embedded_images(value) if value is not None else None


class AdminTicketTemplateReply(BaseModel):
    template_id: int
    is_internal: bool = False


class AdminTicketUpdate(BaseModel):
    status: TicketStatus | None = None
    priority: TicketPriority | None = None
    department_id: int | None = None
    assignee_id: int | None = None


ADMIN_TICKET_STATUS_GROUPS: dict[str, tuple[TicketStatus, ...]] = {
    "pending_staff": (TicketStatus.OPEN, TicketStatus.PENDING, TicketStatus.PROCESSING),
    "waiting_user": (TicketStatus.WAITING_USER, TicketStatus.ANSWERED),
    "resolved": (TicketStatus.RESOLVED,),
    "closed": (TicketStatus.CLOSED,),
}


async def _serialize_admin_ticket(
    db: AsyncSession, ticket: Ticket,
    message_count: int | None = None,
    user_cache: dict[int, dict[str, Any] | None] | None = None,
    instance_cache: dict[int, dict[str, Any] | None] | None = None,
    assignee_cache: dict[int, dict[str, Any] | None] | None = None,
) -> dict[str, Any]:
    user_data = None
    if user_cache is not None:
        user_data = user_cache.get(ticket.user_id, None)
    else:
        user = await db.scalar(select(User).where(User.id == ticket.user_id))
        if user:
            user_data = {
                "id": user.id,
                "username": user.username,
                "nickname": user.nickname,
                "email": user.email,
                "phone": user.phone,
            }
    if message_count is None:
        message_count = await _message_count(db, ticket.id)
    instance_data = None
    if instance_cache is not None:
        instance_data = instance_cache.get(ticket.instance_id) if ticket.instance_id else None
    else:
        instance_data = await _serialize_instance(db, ticket.instance_id)
    assignee_data = None
    if ticket.assignee_id:
        if assignee_cache is not None:
            assignee_data = assignee_cache.get(ticket.assignee_id)
        else:
            assignee = await db.scalar(select(User).where(User.id == ticket.assignee_id))
            assignee_data = _display_user(assignee)
    return {
        "id": ticket.id,
        "ticket_no": ticket.ticket_no,
        "subject": ticket.subject,
        "status": ticket.status,
        "priority": ticket.priority,
        "department_id": ticket.department_id,
        "department_name": ticket.department_name,
        "instance": instance_data,
        "assignee": assignee_data,
        "assigned_at": _dt(ticket.assigned_at),
        "user": user_data,
        "last_message_at": _dt(ticket.last_message_at),
        "last_reply_by": ticket.last_reply_by,
        "unread_count": ticket.admin_unread_count,
        "user_unread_count": ticket.user_unread_count,
        "message_count": message_count,
        "closed_at": _dt(ticket.closed_at),
        "satisfaction": _serialize_satisfaction(ticket),
        "created_at": _dt(ticket.created_at),
        "updated_at": _dt(ticket.updated_at),
    }


async def _serialize_reply_template(db: AsyncSession, item: TicketReplyTemplate) -> dict[str, Any]:
    department_name = None
    if item.department_id:
        department_name = await db.scalar(
            select(TicketDepartment.name).where(TicketDepartment.id == item.department_id)
        )
    return {
        "id": item.id,
        "title": item.title,
        "content": item.content,
        "department_id": item.department_id,
        "department_name": department_name,
        "is_enabled": item.is_enabled,
        "sort_order": item.sort_order,
        "usage_count": item.usage_count,
        "created_by_id": item.created_by_id,
        "updated_by_id": item.updated_by_id,
        "created_at": _dt(item.created_at),
        "updated_at": _dt(item.updated_at),
    }


async def _get_ticket(db: AsyncSession, ticket_id: int) -> Ticket:
    ticket = await db.scalar(select(Ticket).where(Ticket.id == ticket_id))
    if not ticket:
        raise HTTPException(status_code=404, detail="工单不存在")
    return ticket


@router.get("/departments", response_model=ApiResponse)
async def list_departments(
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    departments = (await db.execute(
        select(TicketDepartment).order_by(TicketDepartment.sort_order.asc(), TicketDepartment.id.asc())
    )).scalars().all()
    return ApiResponse(data=[_serialize_department(item) for item in departments])


@router.post("/attachments/images", response_model=ApiResponse)
async def upload_ticket_image(
    file: UploadFile = File(...),
    current_user: User = Depends(_admin),
    db: AsyncSession = Depends(get_db),
):
    return ApiResponse(data=await _save_ticket_image_upload(
        db,
        file,
        uploaded_by_type=TicketSenderType.ADMIN,
        uploaded_by_id=current_user.id,
    ))


@router.get("/staff", response_model=ApiResponse)
async def list_staff(
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    users = (await db.execute(
        select(User)
        .where(
            User.role.in_([UserRole.ADMIN, UserRole.SUPER_ADMIN]),
            User.status == UserStatus.ACTIVE,
        )
        .order_by(User.id.asc())
    )).scalars().all()
    return ApiResponse(data=[_display_user(user) for user in users])


@router.post("/departments", response_model=ApiResponse)
async def create_department(
    body: DepartmentCreate,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    payload = body.model_dump()
    if payload.get("code") is not None:
        payload["code"] = payload["code"].strip() or None
    department = TicketDepartment(**payload)
    db.add(department)
    await db.flush()
    await db.refresh(department)
    return ApiResponse(data=_serialize_department(department))


@router.patch("/departments/{department_id}", response_model=ApiResponse)
async def update_department(
    department_id: int,
    body: DepartmentUpdate,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    department = await db.scalar(select(TicketDepartment).where(TicketDepartment.id == department_id))
    if not department:
        raise HTTPException(status_code=404, detail="工单部门不存在")
    updates = body.model_dump(exclude_unset=True)
    if updates.get("code") is not None:
        updates["code"] = updates["code"].strip() or None
    for key, value in updates.items():
        setattr(department, key, value)
    await db.flush()
    await db.refresh(department)
    return ApiResponse(data=_serialize_department(department))


@router.delete("/departments/{department_id}", response_model=ApiResponse)
async def delete_department(
    department_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    department = await db.scalar(select(TicketDepartment).where(TicketDepartment.id == department_id))
    if not department:
        raise HTTPException(status_code=404, detail="工单部门不存在")
    await db.delete(department)
    await db.flush()
    return ApiResponse(data=None, message="工单部门已删除")


@router.get("/stats", response_model=ApiResponse)
async def ticket_stats(
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    rows = (await db.execute(select(Ticket.status, func.count()).group_by(Ticket.status))).all()
    data = {status.value if hasattr(status, "value") else str(status): int(count) for status, count in rows}
    data["unread"] = int(await db.scalar(select(func.coalesce(func.sum(Ticket.admin_unread_count), 0))) or 0)
    data["rated_count"] = int(
        await db.scalar(select(func.count()).where(Ticket.satisfaction_rating.is_not(None))) or 0
    )
    avg_rating = await db.scalar(select(func.avg(Ticket.satisfaction_rating)).where(Ticket.satisfaction_rating.is_not(None)))
    data["average_rating"] = round(float(avg_rating), 2) if avg_rating is not None else None
    rating_rows = (await db.execute(
        select(Ticket.satisfaction_rating, func.count())
        .where(Ticket.satisfaction_rating.is_not(None))
        .group_by(Ticket.satisfaction_rating)
    )).all()
    for rating, count in rating_rows:
        data[f"satisfaction_{int(rating)}"] = int(count)
    return ApiResponse(data=data)


@router.get("", response_model=ApiResponse)
async def list_tickets(
    keyword: str | None = Query(None),
    status: TicketStatus | None = Query(None, alias="status"),
    status_group: str | None = Query(None),
    department_id: int | None = Query(None),
    assignee_id: int | None = Query(None),
    user_id: int | None = Query(None),
    limit: int = Query(100, ge=1, le=300),
    offset: int = Query(0, ge=0),
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    stmt = select(Ticket).order_by(Ticket.last_message_at.desc(), Ticket.id.desc()).limit(limit).offset(offset)
    if keyword:
        like = f"%{keyword.strip()}%"
        stmt = stmt.where(or_(Ticket.subject.like(like), Ticket.ticket_no.like(like)))
    if status:
        stmt = stmt.where(Ticket.status == status)
    elif status_group and status_group != "all":
        statuses = ADMIN_TICKET_STATUS_GROUPS.get(status_group)
        if not statuses:
            raise HTTPException(status_code=400, detail="未知工单状态分组")
        stmt = stmt.where(Ticket.status.in_(statuses))
    if department_id:
        stmt = stmt.where(Ticket.department_id == department_id)
    if assignee_id:
        stmt = stmt.where(Ticket.assignee_id == assignee_id)
    if user_id:
        stmt = stmt.where(Ticket.user_id == user_id)
    tickets = (await db.execute(stmt)).scalars().all()
    counts = await _message_counts_batch(db, [t.id for t in tickets])
    # Batch-fetch users and instances to avoid N+1
    user_ids = list({t.user_id for t in tickets})
    assignee_ids = list({t.assignee_id for t in tickets if t.assignee_id})
    instance_ids = list({t.instance_id for t in tickets if t.instance_id})
    user_rows = (await db.execute(select(User).where(User.id.in_(user_ids)))).scalars().all() if user_ids else []
    user_cache: dict[int, dict[str, Any] | None] = {uid: None for uid in user_ids}
    for u in user_rows:
        user_cache[u.id] = {"id": u.id, "username": u.username, "nickname": u.nickname, "email": u.email, "phone": u.phone}
    assignee_rows = (await db.execute(select(User).where(User.id.in_(assignee_ids)))).scalars().all() if assignee_ids else []
    assignee_cache: dict[int, dict[str, Any] | None] = {uid: None for uid in assignee_ids}
    for u in assignee_rows:
        assignee_cache[u.id] = _display_user(u)
    instance_cache: dict[int, dict[str, Any] | None] = {}
    for iid in instance_ids:
        instance_cache[iid] = await _serialize_instance(db, iid)
    return ApiResponse(data=[
        await _serialize_admin_ticket(
            db, ticket,
            message_count=counts.get(ticket.id, 0),
            user_cache=user_cache,
            instance_cache=instance_cache,
            assignee_cache=assignee_cache,
        )
        for ticket in tickets
    ])


async def _get_reply_template(db: AsyncSession, template_id: int) -> TicketReplyTemplate:
    template = await db.scalar(select(TicketReplyTemplate).where(TicketReplyTemplate.id == template_id))
    if not template:
        raise HTTPException(status_code=404, detail="回复模板不存在")
    return template


async def _validate_template_department(db: AsyncSession, department_id: int | None) -> None:
    if department_id is None:
        return
    exists = await db.scalar(select(TicketDepartment.id).where(TicketDepartment.id == department_id))
    if not exists:
        raise HTTPException(status_code=404, detail="工单部门不存在")


@router.get("/reply-templates", response_model=ApiResponse)
async def list_reply_templates(
    department_id: int | None = Query(None),
    enabled_only: bool = Query(False),
    include_global: bool = Query(False),
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    stmt = select(TicketReplyTemplate)
    if enabled_only:
        stmt = stmt.where(TicketReplyTemplate.is_enabled.is_(True))
    if department_id:
        if include_global:
            stmt = stmt.where(or_(
                TicketReplyTemplate.department_id == department_id,
                TicketReplyTemplate.department_id.is_(None),
            ))
        else:
            stmt = stmt.where(TicketReplyTemplate.department_id == department_id)
    stmt = stmt.order_by(
        TicketReplyTemplate.sort_order.asc(),
        TicketReplyTemplate.usage_count.desc(),
        TicketReplyTemplate.id.desc(),
    )
    items = (await db.execute(stmt)).scalars().all()
    return ApiResponse(data=[await _serialize_reply_template(db, item) for item in items])


@router.post("/reply-templates", response_model=ApiResponse)
async def create_reply_template(
    body: ReplyTemplateCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(_admin),
):
    await _validate_template_department(db, body.department_id)
    template = TicketReplyTemplate(
        **body.model_dump(),
        created_by_id=current_user.id,
        updated_by_id=current_user.id,
    )
    db.add(template)
    await db.flush()
    await db.refresh(template)
    return ApiResponse(data=await _serialize_reply_template(db, template))


@router.patch("/reply-templates/{template_id}", response_model=ApiResponse)
async def update_reply_template(
    template_id: int,
    body: ReplyTemplateUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(_admin),
):
    template = await _get_reply_template(db, template_id)
    updates = body.model_dump(exclude_unset=True)
    if "department_id" in updates:
        await _validate_template_department(db, updates["department_id"])
    for key, value in updates.items():
        setattr(template, key, value)
    template.updated_by_id = current_user.id
    await db.flush()
    await db.refresh(template)
    return ApiResponse(data=await _serialize_reply_template(db, template))


@router.delete("/reply-templates/{template_id}", response_model=ApiResponse)
async def delete_reply_template(
    template_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    template = await _get_reply_template(db, template_id)
    await db.delete(template)
    await db.flush()
    return ApiResponse(data=None, message="回复模板已删除")


@router.get("/{ticket_id}", response_model=ApiResponse)
async def get_ticket(
    ticket_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    ticket = await _get_ticket(db, ticket_id)
    ticket.admin_unread_count = 0
    await db.flush()
    await db.refresh(ticket)
    messages = (await db.execute(
        select(TicketMessage).where(TicketMessage.ticket_id == ticket.id).order_by(TicketMessage.id.asc())
    )).scalars().all()
    return ApiResponse(data={
        **await _serialize_admin_ticket(db, ticket),
        "messages": await _serialize_messages_batch(db, messages),
        "events": await _load_ticket_events(db, ticket.id, include_internal=True),
    })


@router.patch("/{ticket_id}", response_model=ApiResponse)
async def update_ticket(
    ticket_id: int,
    body: AdminTicketUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(_admin),
):
    ticket = await _get_ticket(db, ticket_id)
    updates = body.model_dump(exclude_unset=True)
    if "department_id" in updates:
        department_id = updates.pop("department_id")
        old_name = ticket.department_name
        department = None
        if department_id is not None:
            department = await db.scalar(select(TicketDepartment).where(TicketDepartment.id == department_id))
            if not department:
                raise HTTPException(status_code=404, detail="工单部门不存在")
        ticket.department_id = department.id if department else None
        ticket.department_name = department.name if department else None
        if old_name != ticket.department_name:
            await _add_ticket_event(
                db,
                ticket,
                TicketSenderType.ADMIN,
                current_user.id,
                TicketEventType.DEPARTMENT_CHANGED,
                from_value=old_name,
                to_value=ticket.department_name,
                message="管理员调整了工单部门",
            )
    if "assignee_id" in updates:
        assignee_id = updates.pop("assignee_id")
        old_assignee_id = ticket.assignee_id
        assignee = None
        if assignee_id is not None:
            assignee = await db.scalar(
                select(User).where(
                    User.id == assignee_id,
                    User.role.in_([UserRole.ADMIN, UserRole.SUPER_ADMIN]),
                    User.status == UserStatus.ACTIVE,
                )
            )
            if not assignee:
                raise HTTPException(status_code=404, detail="负责人不存在或不是有效管理员")
        ticket.assignee_id = assignee.id if assignee else None
        ticket.assigned_at = datetime.now(timezone.utc) if assignee else None
        if old_assignee_id != ticket.assignee_id:
            old_assignee = await db.scalar(select(User).where(User.id == old_assignee_id)) if old_assignee_id else None
            await _add_ticket_event(
                db,
                ticket,
                TicketSenderType.ADMIN,
                current_user.id,
                TicketEventType.ASSIGNED,
                from_value=(old_assignee.nickname or old_assignee.username) if old_assignee else None,
                to_value=(assignee.nickname or assignee.username) if assignee else None,
                message="管理员调整了工单负责人",
                meta={"assignee_id": ticket.assignee_id},
            )
            if assignee:
                await _notify_admins_for_ticket(
                    db,
                    ticket,
                    title="你被分派了一个工单",
                    content=f"工单「{ticket.subject}」已分派给你处理。",
                    level="warning" if ticket.priority in (TicketPriority.HIGH, TicketPriority.URGENT) else "info",
                    prefer_assignee=True,
                )
    if "status" in updates:
        old_status = ticket.status
        ticket.status = updates["status"]
        ticket.closed_at = datetime.now(timezone.utc) if ticket.status in (TicketStatus.CLOSED, TicketStatus.RESOLVED) else None
        if old_status != ticket.status:
            await _add_ticket_event(
                db,
                ticket,
                TicketSenderType.ADMIN,
                current_user.id,
                TicketEventType.STATUS_CHANGED,
                from_value=old_status,
                to_value=ticket.status,
                message="管理员更新了工单状态",
            )
            from app.services import notification_service

            await notification_service.safe_notify(
                db,
                user_id=ticket.user_id,
                category="ticket",
                title="工单状态已更新",
                content=f"您的工单「{ticket.subject}」状态已更新。",
                link=f"/tickets/{ticket.id}",
                action_label="查看工单",
                action_url=f"/tickets/{ticket.id}",
            )
    if "priority" in updates:
        old_priority = ticket.priority
        ticket.priority = updates["priority"]
        if old_priority != ticket.priority:
            await _add_ticket_event(
                db,
                ticket,
                TicketSenderType.ADMIN,
                current_user.id,
                TicketEventType.PRIORITY_CHANGED,
                from_value=old_priority,
                to_value=ticket.priority,
                message="管理员调整了工单优先级",
            )
    await db.flush()
    await db.refresh(ticket)
    return ApiResponse(data=await _serialize_admin_ticket(db, ticket))


@router.post("/{ticket_id}/messages", response_model=ApiResponse)
async def reply_ticket(
    ticket_id: int,
    body: AdminTicketReply,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(_admin),
):
    ticket = await _get_ticket(db, ticket_id)
    if ticket.status in (TicketStatus.CLOSED, TicketStatus.RESOLVED) and not body.is_internal:
        raise HTTPException(status_code=400, detail="工单已结束，请先重新打开")
    if not ticket.assignee_id:
        ticket.assignee_id = current_user.id
        ticket.assigned_at = datetime.now(timezone.utc)
        await _add_ticket_event(
            db,
            ticket,
            TicketSenderType.ADMIN,
            current_user.id,
            TicketEventType.ASSIGNED,
            to_value=current_user.nickname or current_user.username,
            message="管理员认领了工单",
            meta={"assignee_id": current_user.id},
        )
    old_status = ticket.status
    message = await _add_message(
        db,
        ticket,
        TicketSenderType.ADMIN,
        current_user.id,
        body.content,
        body.attachments,
        None if body.is_internal else TicketStatus.WAITING_USER,
        body.is_internal,
    )
    message.attachments = await _claim_message_attachments(
        db,
        ticket=ticket,
        message=message,
        sender_type=TicketSenderType.ADMIN,
        sender_id=current_user.id,
        content=body.content,
        attachments=body.attachments,
    )
    await _add_ticket_event(
        db,
        ticket,
        TicketSenderType.ADMIN,
        current_user.id,
        TicketEventType.INTERNAL_NOTE_ADDED if body.is_internal else TicketEventType.ADMIN_REPLIED,
        from_value=old_status if not body.is_internal else None,
        to_value=TicketStatus.WAITING_USER if not body.is_internal else None,
        message="管理员添加了内部备注" if body.is_internal else "管理员回复了用户",
    )
    if not body.is_internal:
        from app.services import notification_service

        await notification_service.safe_notify(
            db,
            user_id=ticket.user_id,
            category="ticket",
            title="工单有新回复",
            content=f"您的工单「{ticket.subject}」有新的客服回复。",
            link=f"/tickets/{ticket.id}",
            action_label="查看工单",
            action_url=f"/tickets/{ticket.id}",
        )
    return ApiResponse(data=await _serialize_message(db, message))


@router.post("/{ticket_id}/template-replies", response_model=ApiResponse)
async def reply_ticket_with_template(
    ticket_id: int,
    body: AdminTicketTemplateReply,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(_admin),
):
    ticket = await _get_ticket(db, ticket_id)
    template = await _get_reply_template(db, body.template_id)
    if not template.is_enabled:
        raise HTTPException(status_code=400, detail="回复模板已停用")
    if template.department_id and template.department_id != ticket.department_id:
        raise HTTPException(status_code=400, detail="该模板不适用于当前工单部门")
    response = await reply_ticket(
        ticket_id,
        AdminTicketReply(content=template.content, is_internal=body.is_internal),
        db,
        current_user,
    )
    template.usage_count = (template.usage_count or 0) + 1
    template.updated_by_id = current_user.id
    await db.flush()
    return response


@router.post("/{ticket_id}/close", response_model=ApiResponse)
async def close_ticket(
    ticket_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(_admin),
):
    ticket = await _get_ticket(db, ticket_id)
    old_status = ticket.status
    ticket.status = TicketStatus.CLOSED
    ticket.closed_at = datetime.now(timezone.utc)
    await _add_ticket_event(
        db,
        ticket,
        TicketSenderType.ADMIN,
        current_user.id,
        TicketEventType.CLOSED,
        from_value=old_status,
        to_value=TicketStatus.CLOSED,
        message="管理员关闭了工单",
    )
    from app.services import notification_service

    await notification_service.safe_notify(
        db,
        user_id=ticket.user_id,
        category="ticket",
        title="工单已关闭",
        content=f"您的工单「{ticket.subject}」已关闭。",
        link=f"/tickets/{ticket.id}",
        action_label="查看工单",
        action_url=f"/tickets/{ticket.id}",
    )
    await db.flush()
    await db.refresh(ticket)
    return ApiResponse(data=await _serialize_admin_ticket(db, ticket))


@router.post("/{ticket_id}/reopen", response_model=ApiResponse)
async def reopen_ticket(
    ticket_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(_admin),
):
    ticket = await _get_ticket(db, ticket_id)
    old_status = ticket.status
    ticket.status = TicketStatus.OPEN
    ticket.closed_at = None
    await _add_message(
        db,
        ticket,
        TicketSenderType.ADMIN,
        current_user.id,
        "管理员重新打开了工单。",
        None,
        TicketStatus.OPEN,
    )
    await _add_ticket_event(
        db,
        ticket,
        TicketSenderType.ADMIN,
        current_user.id,
        TicketEventType.REOPENED,
        from_value=old_status,
        to_value=TicketStatus.OPEN,
        message="管理员重新打开了工单",
    )
    from app.services import notification_service

    await notification_service.safe_notify(
        db,
        user_id=ticket.user_id,
        category="ticket",
        title="工单已重新打开",
        content=f"您的工单「{ticket.subject}」已由客服重新打开。",
        link=f"/tickets/{ticket.id}",
        action_label="查看工单",
        action_url=f"/tickets/{ticket.id}",
    )
    return ApiResponse(data=await _serialize_admin_ticket(db, ticket))


# Share the user-side thumbnail endpoint for admin use as well
router.add_api_route(
    "/attachments/images/thumbnail",
    ticket_image_thumbnail,
    methods=["GET", "HEAD"],
)
router.add_api_route(
    "/attachments/images/file/{token}",
    ticket_image_file,
    methods=["GET", "HEAD"],
)
router.add_api_route(
    "/attachments/images/thumbnail/{token}",
    ticket_image_thumbnail_by_token,
    methods=["GET", "HEAD"],
)
