# =============================================================================
# 企微IT智能服务台 — 企微入口 SSO（v0.7.1 新增）
# =============================================================================
# 说明: 解决 v0.7.0 hotfix1 用户报告的"企微工作台进入应用也要扫码"问题。
#
# 流程:
#   1. 前端 PortalSelect.vue 加载时检测 navigator.userAgent
#   2. 如果是 MicroMessenger / wxwork / DingTalk 等企微内置浏览器
#      → 调 /api/auth_wecom/sso/init?next=/itdesk/
#   3. 后端生成企微 OAuth2 授权 URL,302 跳转用户去企微授权
#   4. 企微回调 /api/auth_wecom/sso/callback?code=...&state=...
#   5. 用 code 换 userid,查 role (user/agent/admin),生成 token
#   6. 302 跳转到 next 路径 + token query param
#   7. 前端用 token 调 get_current_user 拉身份信息
#
# 配置要求:
#   - 企微管理后台 → 应用 → 网页授权及 JS-SDK → 可信域名: itsupport.servyou.com.cn
#   - 企微管理后台 → 应用 → 网页授权及 JS-SDK → 回调域: itsupport.servyou.com.cn
#   - 环境变量 WECOM_SSO_ENABLED=true 启用(默认 false,避免老用户被打扰)
# =============================================================================

import logging
import secrets
import urllib.parse
from datetime import datetime, timedelta
from typing import Optional

from fastapi import APIRouter, Depends, Query, Request
from fastapi.responses import RedirectResponse
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.config import settings
from app.database import get_db
from app.models.role import Role
from app.models.user_role import UserRole
from app.services.wecom_service import WecomService
from app.utils.response import AppException
from app.dependencies import get_redis

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/auth_wecom", tags=["企微 SSO"])

# OAuth state 在 Redis 的 TTL (5 分钟,够用户授权 + 回调)
OAUTH_STATE_TTL = 300
# SSO token 长度
SSO_TOKEN_BYTES = 32


def _sso_enabled() -> bool:
    """检查是否启用企微 SSO。"""
    import os
    if os.getenv("WECOM_SSO_ENABLED", "false").lower() == "true":
        return True
    if getattr(settings, "wecom_sso_enabled", False):
        return True
    return False


def _get_oauth_callback_url(request: Request) -> str:
    """拼接 OAuth 回调 URL (绝对地址)。

    企微要求 redirect_uri 必须用可信域名(itsupport.servyou.com.cn)。
    不读 request.base_url 因为它可能是 127.0.0.1:8000(开发环境)。
    """
    # 优先用 settings 里的配置
    base = getattr(settings, "wecom_sso_callback_base", None)
    if not base:
        # 兜底: 读环境变量,默认生产域名
        import os
        base = os.getenv("WECOM_SSO_CALLBACK_BASE", "https://itsupport.servyou.com.cn")
    return f"{base.rstrip('/')}/api/auth_wecom/sso/callback"


def _build_oauth_url(state: str, callback_url: str) -> str:
    """拼企微 OAuth2 授权 URL。

    文档: https://developer.work.weixin.qq.com/document/path/91022
    """
    params = {
        "appid": settings.wecom_corp_id,
        "redirect_uri": callback_url,
        "response_type": "code",
        "scope": "snsapi_base",  # 静默授权
        "state": state,
        "agentid": settings.wecom_agent_id,
    }
    query = urllib.parse.urlencode(params)
    return f"https://open.weixin.qq.com/connect/oauth2/authorize?{query}#wechat_redirect"


@router.get("/sso/init")
async def sso_init(
    request: Request,
    next: str = Query("/itdesk/", description="登录后跳转路径"),
    redis_client = Depends(get_redis),
):
    """初始化 SSO: 生成 state,302 跳转到企微 OAuth2 授权页。

    Args:
        next: 登录成功后跳转路径,如 /itdesk/ /itagent/ /itadmin/
    """
    if not _sso_enabled():
        raise AppException(1001, "企微 SSO 未启用, 请用扫码登录")

    # 1. 生成 state(防 CSRF + 携带 next 路径)
    state = secrets.token_urlsafe(24)
    state_payload = {
        "next": next,
        "created_at": datetime.now().isoformat(),
    }
    await redis_client.setex(
        f"wecom_sso:state:{state}",
        OAUTH_STATE_TTL,
        str(state_payload).encode("utf-8"),
    )

    # 2. 拼企微 OAuth URL
    callback_url = _get_oauth_callback_url(request)
    oauth_url = _build_oauth_url(state, callback_url)

    logger.info(f"SSO init: state={state[:8]}..., next={next}")
    return RedirectResponse(url=oauth_url, status_code=302)


@router.get("/sso/callback")
async def sso_callback(
    code: str = Query(..., description="企微 OAuth2 授权 code"),
    state: str = Query(..., description="防 CSRF state"),
    redis_client = Depends(get_redis),
    db: AsyncSession = Depends(get_db),
):
    """企微 OAuth 回调: 用 code 换 userid → 查 role → 生成 token → 跳 next。"""
    # 1. 校验 state(防 CSRF)
    state_key = f"wecom_sso:state:{state}"
    state_raw = await redis_client.get(state_key)
    if not state_raw:
        raise AppException(1002, "SSO state 已过期或无效, 请重新进入")

    # 删除 state(一次性)
    await redis_client.delete(state_key)

    import ast
    state_data = ast.literal_eval(state_raw.decode("utf-8"))
    next_path = state_data.get("next", "/itdesk/")

    # 2. 用 code 换 userid
    wecom = WecomService(redis_client)
    try:
        oauth_info = await wecom.get_oauth_user_info(code)
        user_id = oauth_info.get("userid", "")
        if not user_id:
            raise AppException(1003, "企微 OAuth 返回 userid 为空")

        user_info = await wecom.get_user_info(user_id)
        name = user_info.get("name", user_id)
    except Exception as e:
        logger.error(f"SSO callback 调企微 API 失败: code={code[:8]}..., error={e}")
        raise AppException(1004, f"企微身份识别失败: {str(e)}")
    finally:
        try:
            await wecom.close()
        except Exception:
            pass

    # 3. 查 role (user/agent/admin)
    role_stmt = (
        select(Role)
        .join(UserRole, Role.id == UserRole.role_id)
        .where(UserRole.employee_id == user_id)
    )
    role_result = await db.execute(role_stmt)
    roles = role_result.scalars().all()

    if not roles:
        # 没有绑定角色: 跳"无权限"页
        logger.warning(f"SSO: user_id={user_id} 没绑定任何角色")
        return RedirectResponse(url=f"/itdesk/no-role?user_id={user_id}", status_code=302)

    # 4. 选最高权限角色 (admin > agent > user)
    role_priority = {"admin": 3, "agent": 2, "user": 1}
    best_role = max(roles, key=lambda r: role_priority.get(r.name, 0))
    role_name = best_role.name

    # 5. 生成 SSO token(随机 + Redis 存 8 小时)
    sso_token = secrets.token_urlsafe(SSO_TOKEN_BYTES)
    sso_payload = {
        "user_id": user_id,
        "name": name,
        "role": role_name,
        "created_at": datetime.now().isoformat(),
    }
    import json
    await redis_client.setex(
        f"wecom_sso:token:{sso_token}",
        8 * 3600,  # 8 小时
        json.dumps(sso_payload, ensure_ascii=False).encode("utf-8"),
    )

    # 6. 跳转到 next + token
    separator = "&" if "?" in next_path else "?"
    redirect_url = f"{next_path}{separator}sso_token={sso_token}"

    logger.info(f"SSO 成功: user_id={user_id}, role={role_name}, next={next_path}")
    return RedirectResponse(url=redirect_url, status_code=302)


@router.get("/sso/verify")
async def sso_verify(
    sso_token: str = Query(..., description="SSO token"),
    redis_client = Depends(get_redis),
    db: AsyncSession = Depends(get_db),
):
    """前端用 SSO token 换用户身份(token 一次性使用,用完删除)。"""
    import json
    token_raw = await redis_client.get(f"wecom_sso:token:{sso_token}")
    if not token_raw:
        raise AppException(1005, "SSO token 已过期或无效")

    # 一次性 token(防止泄漏后被滥用)
    await redis_client.delete(f"wecom_sso:token:{sso_token}")

    payload = json.loads(token_raw.decode("utf-8"))
    return {
        "code": 0,
        "data": payload,
    }
