# =============================================================================
# 企微IT智能服务台 — 高危操作守卫服务
# =============================================================================
# 说明：集中处理高危操作（Phase 1.3 task #19）的 OTP 验证状态管理
# 决策来源：otm-secondary-auth.md（2026-06-21 决策）
#
# 核心职责：
#   1. 标记管理员 OTP 验证通过（write）
#   2. 查询管理员 OTP 验证状态（read）
#   3. 撤销管理员 OTP 验证（revoke）
#   4. 列出全部 5 类高危操作白名单（白名单查询）
#
# Redis key 设计：
#   key:    mfa:verified:{employee_id}
#   value:  验证方式（"totp" / "sms_backup"）+ 时间戳
#   TTL:    1800 秒（30 分钟）
#
# 与 dependencies.py 中 require_high_risk_otp 配套使用：
#   - mfa.py 在 /api/mfa/verify 成功后调 mark_verified(...)
#   - require_high_risk_otp 在每个高危端点 Depends 时调 is_verified(...)
# =============================================================================

import json
import logging
from datetime import datetime
from typing import Dict, List, Optional

import redis.asyncio as aioredis

logger = logging.getLogger(__name__)


# -----------------------------------------------------------------------------
# 5 类高危操作白名单（与 dependencies.HIGH_RISK_OPERATIONS 保持一致）
# -----------------------------------------------------------------------------
# 注意:这里再做一次定义是为了让 service 层独立可测,不依赖 dependencies 模块
# （避免循环引用 + 方便单测）
# -----------------------------------------------------------------------------
HIGH_RISK_OPERATIONS_WHITELIST: Dict[str, Dict] = {
    "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",
    },
}


class HighRiskGuard:
    """高危操作守卫服务。

    负责 OTP 验证状态的读写，配套 require_high_risk_otp 依赖使用。

    Attributes:
        redis_client: Redis 异步客户端
        ttl_seconds: OTP 验证有效期（默认 1800 秒 = 30 分钟）
    """

    # Redis key 前缀 — 必须与 dependencies.MFA_VERIFIED_KEY_PREFIX 一致
    KEY_PREFIX = "mfa:verified:"

    # 默认 30 分钟 TTL — 必须与 dependencies.MFA_VERIFIED_TTL_SECONDS 一致
    DEFAULT_TTL_SECONDS = 30 * 60

    def __init__(
        self,
        redis_client: aioredis.Redis,
        ttl_seconds: int = DEFAULT_TTL_SECONDS,
    ):
        """初始化高危操作守卫。

        Args:
            redis_client: Redis 异步客户端
            ttl_seconds: OTP 验证有效期（秒），默认 30 分钟
        """
        self.redis = redis_client
        self.ttl_seconds = ttl_seconds

    def _key(self, employee_id: str) -> str:
        """构造 Redis key。

        Args:
            employee_id: 企微 UserID

        Returns:
            str: Redis key，如 mfa:verified:admin001
        """
        return f"{self.KEY_PREFIX}{employee_id}"

    async def mark_verified(
        self,
        employee_id: str,
        method: str = "totp",
    ) -> bool:
        """标记管理员已通过 OTP 验证。

        由 mfa.py 在 /api/mfa/verify 成功后调用。

        Args:
            employee_id: 企微 UserID
            method: 验证方式，"totp" 或 "sms_backup"

        Returns:
            bool: 是否成功写入
        """
        # value 用 JSON 存验证方式和时间,审计用
        value = json.dumps(
            {
                "method": method,
                "verified_at": datetime.now().isoformat(),
            },
            ensure_ascii=False,
        )

        try:
            await self.redis.setex(
                self._key(employee_id),
                self.ttl_seconds,
                value,
            )
            logger.info(
                f"管理员 {employee_id} OTP 验证通过: method={method}, "
                f"ttl={self.ttl_seconds}s"
            )
            return True
        except Exception as e:
            logger.error(f"写入 OTP verified key 失败: {e}")
            return False

    async def is_verified(self, employee_id: str) -> bool:
        """检查管理员是否在有效期内通过过 OTP。

        由 require_high_risk_otp 依赖调用。

        Args:
            employee_id: 企微 UserID

        Returns:
            bool: 是否已通过 OTP 验证
        """
        try:
            value = await self.redis.get(self._key(employee_id))
            # 空字符串 / None / 空 bytes 全部算"未通过"
            if not value:
                return False
            return True
        except Exception as e:
            logger.error(f"读取 OTP verified key 失败: {e}")
            # Redis 故障时保守放行?不,安全优先,默认不通过
            return False

    async def get_verification_info(
        self,
        employee_id: str,
    ) -> Optional[Dict]:
        """获取管理员 OTP 验证详情（含方式和时间）。

        用于审计/前端展示"上次验证时间"。

        Args:
            employee_id: 企微 UserID

        Returns:
            Optional[Dict]: 验证信息 dict，未验证返回 None
            示例: {"method": "totp", "verified_at": "2026-06-21T15:30:00"}
        """
        try:
            value = await self.redis.get(self._key(employee_id))
            if not value:
                return None
            if isinstance(value, bytes):
                value = value.decode("utf-8")
            return json.loads(value)
        except Exception as e:
            logger.error(f"解析 OTP verified info 失败: {e}")
            return None

    async def revoke(self, employee_id: str) -> bool:
        """撤销管理员 OTP 验证（强制重新验证）。

        场景:安全事件触发 / 管理员主动撤销 / 登出时清理。

        Args:
            employee_id: 企微 UserID

        Returns:
            bool: 是否成功撤销（key 不存在也算成功）
        """
        try:
            deleted = await self.redis.delete(self._key(employee_id))
            logger.info(
                f"管理员 {employee_id} OTP 验证已撤销: deleted={deleted}"
            )
            return True
        except Exception as e:
            logger.error(f"撤销 OTP verified key 失败: {e}")
            return False

    async def refresh_ttl(self, employee_id: str) -> bool:
        """刷新 OTP 验证的 TTL（滑动窗口）。

        每次高危操作通过守卫后调用，延长 30 分钟有效期。
        已在 dependencies.require_high_risk_otp 内联调用,这里冗余暴露给 service 层。

        Args:
            employee_id: 企微 UserID

        Returns:
            bool: 是否刷新成功
        """
        try:
            # 只有 key 存在时才刷新 TTL,防止误创建空 key
            value = await self.redis.get(self._key(employee_id))
            if not value:
                return False
            await self.redis.expire(self._key(employee_id), self.ttl_seconds)
            return True
        except Exception as e:
            logger.error(f"刷新 OTP verified TTL 失败: {e}")
            return False

    @staticmethod
    def get_whitelist() -> Dict[str, Dict]:
        """获取 5 类高危操作白名单。

        静态方法,供前端文档化展示"哪些操作需要 OTP"。

        Returns:
            Dict[str, Dict]: 白名单字典
        """
        return HIGH_RISK_OPERATIONS_WHITELIST.copy()

    @staticmethod
    def is_valid_category(category: str) -> bool:
        """检查 category 是否在 5 类白名单内。

        Args:
            category: 类别标识

        Returns:
            bool: 是否合法
        """
        return category in HIGH_RISK_OPERATIONS_WHITELIST

    @staticmethod
    def list_categories() -> List[str]:
        """列出全部 5 类高危操作标识。

        Returns:
            List[str]: category 列表
        """
        return list(HIGH_RISK_OPERATIONS_WHITELIST.keys())


# -----------------------------------------------------------------------------
# 工厂函数：方便在非 FastAPI DI 场景使用
# -----------------------------------------------------------------------------
def create_high_risk_guard(redis_client: aioredis.Redis) -> HighRiskGuard:
    """创建 HighRiskGuard 实例。

    Args:
        redis_client: Redis 异步客户端

    Returns:
        HighRiskGuard: 守卫服务实例
    """
    return HighRiskGuard(redis_client)