# =============================================================================
# 企微IT智能服务台 — 统一认证依赖
# =============================================================================
# 说明：提供统一的认证依赖函数，支持：
#   1. get_current_user: 获取当前用户信息（包含角色）
#   2. require_role: 角色验证装饰器
#   3. require_admin: 管理员权限验证
# =============================================================================

import inspect
import json
import logging
from dataclasses import dataclass
from functools import wraps
from typing import List, Optional

import redis.asyncio as aioredis
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

from app.config import settings
from app.services.token_service import TokenService
from app.utils.response import AppException

logger = logging.getLogger(__name__)

# HTTP Bearer 认证方案
security = HTTPBearer()


@dataclass
class UserInfo:
    """用户信息数据类。

    Attributes:
        employee_id: 企微 UserID
        name: 用户姓名
        department: 部门
        avatar: 头像URL
        roles: 角色列表
        current_role: 当前选择的角色
        login_source: 登录来源
    """

    employee_id: str
    name: str
    department: str
    avatar: str
    roles: List[str]
    current_role: str
    login_source: str


# Redis 连接池（单例）
_redis_pool: Optional[aioredis.Redis] = None


async def get_redis() -> aioredis.Redis:
    """获取 Redis 连接。

    Returns:
        aioredis.Redis: Redis 异步客户端
    """
    global _redis_pool
    if _redis_pool is None:
        _redis_pool = settings.create_redis_client()
    return _redis_pool


# 共享服务实例（用于 wecom_callback.py 等模块）
# 这些函数提供同步获取服务实例的方式，用于非 FastAPI DI 的场景
def get_shared_redis() -> aioredis.Redis:
    """获取 Redis 客户端（同步版本，用于非 async 场景）。

    Returns:
        aioredis.Redis: Redis 客户端实例
    """
    return settings.create_redis_client()


def get_shared_wecom_service():
    """获取 WecomService 共享实例。

    Returns:
        WecomService: 企微服务实例
    """
    from app.services.wecom_service import WecomService
    return WecomService(settings.create_redis_client())


def get_shared_ai_handler():
    """获取 AIHandler 共享实例。

    Returns:
        AIHandler: AI 处理器实例
    """
    from app.services.ai_handler import AIHandler
    from app.services.ai_service import AIService
    return AIHandler(ai_service=AIService())


# FastAPI Depends 函数（用于路由依赖注入）
async def dep_redis() -> aioredis.Redis:
    """Redis 客户端依赖注入。

    Returns:
        aioredis.Redis: Redis 异步客户端
    """
    return await get_redis()


def dep_wecom_service():
    """WecomService 依赖注入。

    Returns:
        WecomService: 企微服务实例
    """
    from app.services.wecom_service import WecomService
    return WecomService(settings.create_redis_client())


def dep_ai_handler():
    """AIHandler 依赖注入。

    Returns:
        AIHandler: AI 处理器实例
    """
    from app.services.ai_handler import AIHandler
    from app.services.ai_service import AIService
    return AIHandler(ai_service=AIService())


def dep_wingman_service():
    """WingmanService 依赖注入。

    Returns:
        WingmanService: AI Wingman 服务实例
    """
    from app.services.wingman_service import WingmanService
    return WingmanService()


# 应用生命周期管理函数
async def init_shared_services():
    """初始化共享服务（应用启动时调用）。

    创建 Redis 连接池，初始化共享服务实例。
    """
    global _redis_pool
    _redis_pool = settings.create_redis_client()
    logger.info("共享服务初始化完成")


async def cleanup_shared_services():
    """清理共享服务（应用关闭时调用）。

    关闭 Redis 连接池。
    """
    global _redis_pool
    if _redis_pool:
        await _redis_pool.close()
        _redis_pool = None
        logger.info("共享服务清理完成")


async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security),
) -> UserInfo:
    """统一认证依赖：从 Token 获取用户信息。

    支持新旧两种 Token 格式。

    Args:
        credentials: HTTP Bearer Token

    Returns:
        UserInfo: 用户信息

    Raises:
        HTTPException: Token 无效或已过期
    """
    token = credentials.credentials

    # 获取 Redis 连接
    redis_client = await get_redis()

    # 创建 Token 服务
    token_service = TokenService(redis_client)

    # 获取用户信息
    user_info = await token_service.get_user_info(token)

    if not user_info:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token 无效或已过期",
            headers={"WWW-Authenticate": "Bearer"},
        )

    return UserInfo(
        employee_id=user_info["employee_id"],
        name=user_info.get("name", ""),
        department=user_info.get("department", ""),
        avatar=user_info.get("avatar", ""),
        roles=user_info.get("roles", ["user"]),
        current_role=user_info.get("current_role", "user"),
        login_source=user_info.get("login_source", "portal"),
    )


def require_role(*required_roles: str):
    """角色验证装饰器。

    检查用户是否拥有指定角色之一。

    Args:
        *required_roles: 允许的角色列表

    Returns:
        装饰器函数

    Example:
        @router.get("/api/admin/dashboard")
        @require_role("admin")
        async def get_dashboard(current_user: UserInfo = Depends(get_current_user)):
            pass
    """

    def decorator(func):
        # 合并 func 签名 + current_user 参数,让 FastAPI 能正确解析 Depends
        # (v0.5.6 修复:之前用 @wraps,FastAPI 看到的是 __wrapped__ 的签名,
        #  没有 current_user,导致 Depends 默认值未被解析,current_user 实际是 Depends 对象)
        sig = inspect.signature(func)
        params = list(sig.parameters.values())
        params.append(
            inspect.Parameter(
                'current_user',
                inspect.Parameter.KEYWORD_ONLY,
                annotation=UserInfo,
                default=Depends(get_current_user),
            )
        )
        new_sig = sig.replace(parameters=params)

        @wraps(func)
        async def wrapper(*args, **kwargs):
            # FastAPI 已经把 current_user 注入了 kwargs
            current_user = kwargs.pop('current_user')

            # 检查用户是否有任一所需角色
            user_roles = set(current_user.roles)
            required = set(required_roles)

            if not user_roles.intersection(required):
                logger.warning(
                    f"用户 {current_user.employee_id} 角色不足: "
                    f"拥有 {current_user.roles}, 需要 {required_roles}"
                )
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail=f"需要以下角色之一: {', '.join(required_roles)}",
                )

            return await func(*args, current_user=current_user, **kwargs)

        # 关键:让 FastAPI 用合并后的签名,这样它能看到 current_user 这个 Depends 参数
        wrapper.__signature__ = new_sig
        return wrapper

    return decorator


def require_admin(func):
    """管理员权限验证装饰器。

    等同于 @require_role("admin")。

    Example:
        @router.get("/api/admin/dashboard")
        @require_admin
        async def get_dashboard(current_user: UserInfo = Depends(get_current_user)):
            pass
    """
    return require_role("admin")(func)


# =============================================================================
# 细粒度权限装饰器 (v0.7.1 task #86 — RBAC 5 角色 × 4 资源 × 4 操作 × 3 范围)
# =============================================================================
# 权限字符串格式: "resource:action:scope"
# 例: "conversation:read:all"
#
# 用法:
#   @router.get("/api/admin/agents")
#   @require_permission("agent:read:all")
#   async def list_agents(...): ...
#
# 行为:
#   1. 装饰器只检查"是否拥有权限字符串",不直接执行 DB 查询
#   2. 实际检查在 rbac_service.check_permission() 里
#   3. 用户的权限从 UserInfo.permissions 字段读(由 get_current_user 解析 token 时填入)
# =============================================================================

def require_permission(
    resource: str,
    action: str,
    scope: str = "own",
):
    """细粒度权限验证装饰器(v0.7.1 task #86)。

    Args:
        resource: 资源(conversation/agent/system_config/audit_log)
        action: 操作(read/create/update/delete)
        scope: 数据范围(own/department/all)

    Example:
        @router.get("/api/admin/agents")
        @require_permission("agent", "read", "all")
        async def list_agents(current_user: UserInfo = Depends(get_current_user)):
            ...
    """
    perm_string = f"{resource}:{action}:{scope}"

    def decorator(func):
        sig = inspect.signature(func)
        params = list(sig.parameters.values())
        params.append(
            inspect.Parameter(
                'current_user',
                inspect.Parameter.KEYWORD_ONLY,
                annotation=UserInfo,
                default=Depends(get_current_user),
            )
        )
        new_sig = sig.replace(parameters=params)

        @wraps(func)
        async def wrapper(*args, **kwargs):
            current_user = kwargs.pop('current_user')

            # 拉用户所有角色的 permissions
            # 注: UserInfo.roles 是角色名列表,permissions 是 {role: [perm]} 字典
            # 首次实现简化: 角色判断 + admin 通配符
            # 完整实现需要查 DB 拉 permissions,见 rbac_service.check_permission

            user_roles = set(current_user.roles or [])

            # 1. admin 角色直通(通配符 *:*:all)
            if "admin" in user_roles:
                return await func(*args, current_user=current_user, **kwargs)

            # 2. 其他角色: 走 rbac_service.check_permission
            #    简化: 这里只看角色名,不查 DB(性能考虑)
            #    实际生产可加缓存或预加载到 token
            from app.services.rbac_service import (
                ROLE_PERMISSIONS,
                check_permission,
            )
            # 把 ROLE_PERMISSIONS 转成 {role_name: [perm_string]} 格式
            user_perms_dict = {
                role: [f"{r}:{a}:{s}" for (r, a, s) in perms]
                for role, perms in ROLE_PERMISSIONS.items()
            }

            has_perm = check_permission(
                user_roles=list(user_roles),
                user_permissions=user_perms_dict,
                required_resource=resource,
                required_action=action,
                required_scope=scope,
            )

            if not has_perm:
                logger.warning(
                    f"用户 {current_user.employee_id} 权限不足: "
                    f"角色 {list(user_roles)}, 缺 {perm_string}"
                )
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail=f"权限不足: 需要 {perm_string}",
                )

            return await func(*args, current_user=current_user, **kwargs)

        wrapper.__signature__ = new_sig
        return wrapper

    return decorator


# =============================================================================
# 高危操作 OTP 守卫依赖（Phase 1.3 task #19）
# =============================================================================
# 决策来源：otm-secondary-auth.md
# 触发场景：管理员执行 5 类高危操作前，必须在 30 分钟内通过 OTP 二次验证
# 验证流程：
#   1. 管理员先调 /api/mfa/verify 校验 TOTP 验证码（蜂鸟 SMS 备用）
#   2. 验证通过后 mfa.py 在 Redis 写 mfa:verified:{employee_id}，TTL=1800 秒
#   3. 高危操作端点 Depends(require_high_risk_otp) 时：
#      - 检查角色：admin（403 否则）
#      - 检查 Redis key：mfa:verified:{employee_id}（不存在则 raise 2001）
#   4. 前端收到 2001 → 弹 OTP 输入框 → 重试
#
# 5 类高危操作清单（与 otm-secondary-auth.md 对齐）：
#   1. role_change          改权限        POST /api/admin/roles/assign
#   2. config_change        改配置        PUT  /api/admin/configs/{key}
#   3. data_export          导出数据      GET  /api/admin/export/*
#   4. account_disable      封号          DELETE /api/admin/agents/{id}
#   5. account_create_reset 新增账号/重置 POST /api/admin/agents, /api/admin/mfa/reset/{id}
# =============================================================================

# 高危操作白名单（category → 元数据）
# 用于演示路由 + 文档化，前端可读此表知道哪些操作需要 OTP
HIGH_RISK_OPERATIONS = {
    "role_change": {
        "category": "改权限",
        "require_otp": True,
        "examples": ["POST /api/admin/roles/assign", "POST /api/admin/roles/revoke"],
        "description": "分配或撤销用户角色",
    },
    "config_change": {
        "category": "改配置",
        "require_otp": True,
        "examples": ["PUT /api/admin/configs/{key}"],
        "description": "修改系统配置项",
    },
    "data_export": {
        "category": "导出数据",
        "require_otp": True,
        "examples": ["GET /api/admin/export/*"],
        "description": "导出敏感数据（会话、坐席统计等）",
    },
    "account_disable": {
        "category": "封号",
        "require_otp": True,
        "examples": ["DELETE /api/admin/agents/{id}"],
        "description": "禁用/删除坐席账号",
    },
    "account_create_reset": {
        "category": "新增账号/重置",
        "require_otp": True,
        "examples": ["POST /api/admin/agents", "POST /api/admin/mfa/reset/{id}"],
        "description": "新增坐席或重置 MFA",
    },
}

# MFA 验证通过的 Redis key 前缀
# 由 mfa.py 在 /api/mfa/verify 成功后写入，TTL=1800 秒
MFA_VERIFIED_KEY_PREFIX = "mfa:verified:"

# MFA 验证有效期（30 分钟，与 otm-secondary-auth.md 决策一致）
MFA_VERIFIED_TTL_SECONDS = 30 * 60


async def require_high_risk_otp(
    current_user: UserInfo = Depends(get_current_user),
) -> UserInfo:
    """高危操作 OTP 守卫（管理员触发高危操作前必过）。

    业务规则（来自 otm-secondary-auth.md 2026-06-21 决策）：
      1. 仅 admin 角色需要过 OTP（agent/user 直接 403）
      2. 必须在 30 分钟内通过 /api/mfa/verify 校验过 OTP
      3. 验证失败的 key 不算（空字符串/已过期）

    鉴权流程：
      - 请求携带 Bearer Token → get_current_user 解析 UserInfo
      - 检查 UserInfo.roles 是否含 "admin"（否则 4003 仅管理员）
      - 检查 Redis mfa:verified:{employee_id} 是否存在（否则 2001 需 OTP）

    Args:
        current_user: 当前用户（FastAPI 自动注入）

    Returns:
        UserInfo: 当前用户（已通过 OTP 守卫）

    Raises:
        AppException(4003, "仅管理员可执行此操作"): 非管理员角色
        AppException(2001, "高危操作需要 OTP 二次验证"): admin 但未在 30 分钟内过 OTP
    """
    # 第 1 关:角色检查 - 只有 admin 才需要 OTP 验证
    # 注: current_role 是当前激活角色,roles 是全部角色,两者都查(双保险)
    user_roles = current_user.roles or []
    is_admin = (
        current_user.current_role == "admin"
        or "admin" in user_roles
    )
    if not is_admin:
        logger.warning(
            f"用户 {current_user.employee_id} 尝试高危操作但不是 admin: "
            f"current_role={current_user.current_role}, roles={user_roles}"
        )
        raise AppException(
            code=4003,
            message="仅管理员可执行此高危操作",
        )

    # 第 2 关:OTP 验证标记检查 - Redis mfa:verified:{employee_id}
    redis_client = await get_redis()
    verified_key = f"{MFA_VERIFIED_KEY_PREFIX}{current_user.employee_id}"
    verified = await redis_client.get(verified_key)

    # 注:空字符串/null/bytes 都算"未通过"
    if not verified:
        logger.warning(
            f"管理员 {current_user.employee_id} 未通过 OTP 守卫: "
            f"Redis key '{verified_key}' 不存在或已过期"
        )
        raise AppException(
            code=2001,
            message="高危操作需要 OTP 二次验证，请先完成 OTP 验证",
        )

    # 防御性:刷新 TTL（滑动窗口）—— 如果管理员持续在做高危操作，
    # 不用反复输 OTP。但要求单次操作 < 30 分钟间隔。
    # 注: mfa.py 写入时已设 1800 秒 TTL，这里只在存在时刷新
    if hasattr(redis_client, "expire"):
        try:
            await redis_client.expire(verified_key, MFA_VERIFIED_TTL_SECONDS)
        except Exception as e:
            # 刷新失败不影响主流程，仅记录
            logger.debug(f"刷新 OTP verified TTL 失败: {e}")

    logger.info(
        f"管理员 {current_user.employee_id} 通过 OTP 守卫，执行高危操作"
    )
    return current_user
