# =============================================================================
# 企微IT智能服务台 — 扫码登录 API
# =============================================================================
# 说明：扫码登录是 Phase 1.1 的核心功能,用于替代坐席端"用户名密码+企微
# OAuth"双因素登录,提供"用企微 App 扫一扫登录浏览器坐席端"的体验。
#
# 完整流程:
#   ┌─────────┐  create  ┌─────────────┐  scan  ┌──────────┐
#   │ 浏览器  │ ───────→ │ ticket(120s)│ ←───── │ 企微 App │
#   │ 前端    │ ←─────── │ +OAuth URL  │ OAuth  │ 扫码授权  │
#   └─────────┘  qrcode_url  └─────────────┘ code   └──────────┘
#        │                          │                     │
#        │ poll                     │ scan                │
#        │ waiting/scanned          │ 写 scan:{ticket}    │
#        │                          ↓                     │
#        │                  ┌────────────────┐           │
#        │                  │ 已登录坐席(企微)│ confirm   │
#        │                  │ 点"确认登录"按钮 │ ────────→ │
#        │                  └────────────────┘           │
#        │                          │                     │
#        │ poll                     │ confirm             │
#        │ confirmed+token          │ 写 confirm:{ticket} │
#        ↓                          ↓                     │
#   拿到 token,跳坐席端主页                              │
#
# 端点列表(4 个):
#   POST /api/auth_qrcode/create              — 浏览器前端生成 ticket
#   GET  /api/auth_qrcode/poll/{ticket}        — 前端轮询扫码状态
#   POST /api/auth_qrcode/scan                 — 企微 OAuth2 回调(接收 code)
#   POST /api/auth_qrcode/confirm              — 当前登录坐席点确认
#
# 鉴权说明:
#   - create / scan / poll: 无需登录(浏览器刚加载登录页,用户未登录)
#   - confirm: 需要已登录坐席点确认(角色: agent / admin)
#   - 票据状态全部存 Redis,TTL 到期自动失效,无 DB 表
# =============================================================================

import logging
from typing import Optional

import redis.asyncio as aioredis
from fastapi import APIRouter, Depends, Path

from app.config import settings
from app.dependencies import dep_redis, get_current_user, UserInfo
from app.schemas.qrcode import (
    QrcodeConfirmRequest,
    QrcodeConfirmResponse,
    QrcodeCreateResponse,
    QrcodePollResponse,
    QrcodeScanRequest,
    QrcodeScanResponse,
)
from app.services.qrcode_service import QrcodeService
from app.utils.response import AppException, success_response

logger = logging.getLogger(__name__)

# 创建路由器
# prefix="/auth_qrcode" + tags=["扫码登录"] 用于 Swagger 分组
router = APIRouter(prefix="/auth_qrcode", tags=["扫码登录"])


def _get_qrcode_service(redis_client: aioredis.Redis) -> QrcodeService:
    """工厂函数: 构造扫码登录业务服务。

    拆出来便于测试时 monkey-patch,以及后续接入 DI。
    """
    return QrcodeService(redis_client)


# --------------------------------------------------------------------------
# POST /api/auth_qrcode/create — 创建扫码登录票据
# --------------------------------------------------------------------------
@router.post("/create", response_model=None)
async def create_qrcode(
    redis_client: aioredis.Redis = Depends(dep_redis),
):
    """创建扫码登录票据。

    无需鉴权(用户尚未登录,正在登录页)。
    返回 ticket + 企微 OAuth2 授权 URL,前端渲染二维码。

    Returns:
        Dict: 统一响应格式,data 字段是 QrcodeCreateResponse
    """
    try:
        service = _get_qrcode_service(redis_client)
        result = await service.create_ticket()

        return success_response(data={
            "ticket": result["ticket"],
            "qrcode_url": result["qrcode_url"],
            "qrcode_png_base64": result["qrcode_png_base64"],
            "expires_in": result["expires_in"],
            "expires_at": result["expires_at"].isoformat(),
        })

    except Exception as e:
        logger.error(f"创建扫码票据异常: {e}", exc_info=True)
        raise AppException(1005, f"创建扫码票据失败: {str(e)}")


# --------------------------------------------------------------------------
# GET /api/auth_qrcode/poll/{ticket} — 前端轮询扫码状态
# --------------------------------------------------------------------------
@router.get("/poll/{ticket}", response_model=None)
async def poll_qrcode(
    ticket: str = Path(..., description="扫码登录票据"),
    redis_client: aioredis.Redis = Depends(dep_redis),
):
    """轮询扫码状态。

    无需鉴权(浏览器未登录态访问)。

    状态机:
      - waiting:   ticket 有效,等待扫码
      - scanned:   已扫码,等待 confirm
      - confirmed: 已确认,返回 token
      - expired:   ticket 过期/不存在

    Returns:
        Dict: 统一响应格式,data 字段是 QrcodePollResponse
    """
    try:
        service = _get_qrcode_service(redis_client)
        result = await service.get_poll_state(ticket)

        return success_response(data={
            "status": result["status"],
            "employee_id": result.get("employee_id"),
            "name": result.get("name"),
            "token": result.get("token"),
        })

    except Exception as e:
        logger.error(f"轮询扫码状态异常: ticket={ticket[:8]}..., error={e}", exc_info=True)
        raise AppException(1005, f"轮询扫码状态失败: {str(e)}")


# --------------------------------------------------------------------------
# POST /api/auth_qrcode/scan — 企微 OAuth code 回调
# --------------------------------------------------------------------------
@router.post("/scan", response_model=None)
async def scan_qrcode(
    body: QrcodeScanRequest,
    redis_client: aioredis.Redis = Depends(dep_redis),
):
    """处理企微 OAuth2 扫码回调。

    无需鉴权(此端点被企微服务器回调,带 code + ticket)。
    用 code 换取企微 userid,然后写 Redis scan:{ticket} 等待 confirm 端点。

    dev 模式: code 形如 "dev:dev-user-001",跳过企微 API 调用。

    Args:
        body: 包含 ticket 和 code

    Returns:
        Dict: 统一响应格式,data 字段是 QrcodeScanResponse
    """
    try:
        service = _get_qrcode_service(redis_client)
        result = await service.process_scan(ticket=body.ticket, code=body.code)

        return success_response(data={
            "success": result["success"],
            "message": result["message"],
        })

    except ValueError as ve:
        # 票据过期/不存在 → 业务错误
        logger.warning(f"扫码业务错误: {ve}")
        raise AppException(1003, str(ve))
    except Exception as e:
        logger.error(f"扫码处理异常: ticket={body.ticket[:8]}..., error={e}", exc_info=True)
        raise AppException(1005, f"扫码处理失败: {str(e)}")


# --------------------------------------------------------------------------
# POST /api/auth_qrcode/confirm — 当前已登录坐席确认授权
# --------------------------------------------------------------------------
@router.post("/confirm", response_model=None)
async def confirm_qrcode(
    body: QrcodeConfirmRequest,
    current_user: UserInfo = Depends(get_current_user),
    redis_client: aioredis.Redis = Depends(dep_redis),
):
    """处理当前已登录坐席的扫码确认授权。

    需要鉴权: 只有已登录的坐席/管理员能确认授权。
    把扫码用户身份变成可登录 Token(roles=['agent']),
    写 Redis confirm:{ticket},前端 poll 拿到后跳坐席主页。

    otp_code: admin 场景下可选,Phase 1.1 仅记录日志,
              真实 OTP 校验留给 Phase 2.1(参考 agents.py:272-274 的 totp.verify)。

    Args:
        body: 包含 ticket 和 otp_code(可选)
        current_user: 当前已登录用户(由 get_current_user 注入)
        redis_client: Redis 客户端

    Returns:
        Dict: 统一响应格式,data 字段是 QrcodeConfirmResponse
    """
    try:
        service = _get_qrcode_service(redis_client)
        result = await service.process_confirm(
            ticket=body.ticket,
            current_user_id=current_user.employee_id,
            current_user_name=current_user.name,
            current_roles=current_user.roles,
            otp_code=body.otp_code,
        )

        return success_response(data={
            "token": result["token"],
            "employee_id": result["employee_id"],
            "name": result["name"],
            "roles": result["roles"],
            "require_otp": result.get("require_otp"),
        })

    except ValueError as ve:
        # 票据过期/未扫码 → 业务错误
        logger.warning(
            f"扫码确认业务错误: ticket={body.ticket[:8]}..., "
            f"current_user={current_user.employee_id}, error={ve}"
        )
        raise AppException(1003, str(ve))
    except Exception as e:
        logger.error(
            f"扫码确认异常: ticket={body.ticket[:8]}..., "
            f"current_user={current_user.employee_id}, error={e}",
            exc_info=True,
        )
        raise AppException(1005, f"扫码确认失败: {str(e)}")