"""
钉钉认证服务

提供钉钉免密登录功能，从数据库读取配置
"""

import json
import time
from typing import Optional, Dict, Any, Tuple

import httpx
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.logger import get_logger
from app.core.security import create_access_token, create_refresh_token
from app.models.user import User
from app.schemas.auth import Token
from app.services.user_service import UserService

logger = get_logger(__name__)

# 钉钉API地址
DINGTALK_API_BASE = "https://oapi.dingtalk.com"


class DingtalkAuthService:
    """钉钉认证服务"""

    def __init__(self, db: AsyncSession):
        self.db = db
        self.user_service = UserService(db)
        self._access_token_cache: Dict[int, Tuple[str, float]] = {}  # tenant_id -> (token, expire_time)

    async def get_dingtalk_config(self, tenant_id: int) -> Dict[str, str]:
        """
        从数据库获取钉钉配置
        
        Args:
            tenant_id: 租户ID
            
        Returns:
            配置字典 {app_key, app_secret, agent_id, corp_id}
        """
        result = await self.db.execute(
            text("""
                SELECT config_key, config_value 
                FROM tenant_configs 
                WHERE tenant_id = :tenant_id AND config_group = 'dingtalk'
            """),
            {"tenant_id": tenant_id}
        )
        rows = result.fetchall()
        
        config = {}
        key_mapping = {
            "DINGTALK_APP_KEY": "app_key",
            "DINGTALK_APP_SECRET": "app_secret",
            "DINGTALK_AGENT_ID": "agent_id",
            "DINGTALK_CORP_ID": "corp_id",
        }
        
        for row in rows:
            if row[0] in key_mapping:
                config[key_mapping[row[0]]] = row[1]
        
        return config

    async def is_dingtalk_login_enabled(self, tenant_id: int) -> bool:
        """
        检查钉钉免密登录功能是否启用
        
        Args:
            tenant_id: 租户ID
            
        Returns:
            是否启用
        """
        # 先查租户级别的配置
        result = await self.db.execute(
            text("""
                SELECT is_enabled FROM feature_switches 
                WHERE feature_code = 'dingtalk_login' 
                AND (tenant_id = :tenant_id OR tenant_id IS NULL)
                ORDER BY tenant_id DESC
                LIMIT 1
            """),
            {"tenant_id": tenant_id}
        )
        row = result.fetchone()
        
        if row:
            return bool(row[0])
        
        return False

    async def get_access_token(self, tenant_id: int) -> str:
        """
        获取钉钉访问令牌（带内存缓存）
        
        Args:
            tenant_id: 租户ID
            
        Returns:
            access_token
            
        Raises:
            Exception: 获取失败时抛出异常
        """
        # 检查缓存
        if tenant_id in self._access_token_cache:
            token, expire_time = self._access_token_cache[tenant_id]
            if time.time() < expire_time - 300:  # 提前5分钟刷新
                return token
        
        # 获取配置
        config = await self.get_dingtalk_config(tenant_id)
        if not config.get("app_key") or not config.get("app_secret"):
            raise ValueError("钉钉配置不完整，请在管理后台配置AppKey和AppSecret")
        
        # 调用钉钉API获取token
        url = f"{DINGTALK_API_BASE}/gettoken"
        params = {
            "appkey": config["app_key"],
            "appsecret": config["app_secret"],
        }
        
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.get(url, params=params)
            data = response.json()
        
        if data.get("errcode") != 0:
            error_msg = data.get("errmsg", "未知错误")
            logger.error(f"获取钉钉access_token失败: {error_msg}")
            raise Exception(f"获取钉钉access_token失败: {error_msg}")
        
        access_token = data["access_token"]
        expires_in = data.get("expires_in", 7200)
        
        # 缓存token
        self._access_token_cache[tenant_id] = (access_token, time.time() + expires_in)
        
        logger.info(f"获取钉钉access_token成功，有效期: {expires_in}秒")
        return access_token

    async def get_user_info_by_code(self, tenant_id: int, code: str) -> Dict[str, Any]:
        """
        通过免登码获取钉钉用户信息
        
        Args:
            tenant_id: 租户ID
            code: 免登授权码
            
        Returns:
            用户信息 {userid, name, ...}
            
        Raises:
            Exception: 获取失败时抛出异常
        """
        access_token = await self.get_access_token(tenant_id)
        
        url = f"{DINGTALK_API_BASE}/topapi/v2/user/getuserinfo"
        params = {"access_token": access_token}
        payload = {"code": code}
        
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.post(url, params=params, json=payload)
            data = response.json()
        
        if data.get("errcode") != 0:
            error_msg = data.get("errmsg", "未知错误")
            logger.error(f"通过code获取钉钉用户信息失败: {error_msg}")
            raise Exception(f"获取钉钉用户信息失败: {error_msg}")
        
        result = data.get("result", {})
        logger.info(f"获取钉钉用户信息成功: userid={result.get('userid')}, name={result.get('name')}")
        
        return result

    async def get_user_detail(self, tenant_id: int, userid: str) -> Dict[str, Any]:
        """
        获取钉钉用户详细信息
        
        Args:
            tenant_id: 租户ID
            userid: 钉钉用户ID
            
        Returns:
            用户详细信息
        """
        access_token = await self.get_access_token(tenant_id)
        
        url = f"{DINGTALK_API_BASE}/topapi/v2/user/get"
        params = {"access_token": access_token}
        payload = {"userid": userid}
        
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.post(url, params=params, json=payload)
            data = response.json()
        
        if data.get("errcode") != 0:
            error_msg = data.get("errmsg", "未知错误")
            logger.warning(f"获取钉钉用户详情失败: {error_msg}")
            return {}
        
        return data.get("result", {})

    async def dingtalk_login(self, tenant_id: int, code: str) -> Tuple[User, Token]:
        """
        钉钉免密登录主流程
        
        Args:
            tenant_id: 租户ID
            code: 免登授权码
            
        Returns:
            (用户对象, Token对象)
            
        Raises:
            Exception: 登录失败时抛出异常
        """
        # 1. 检查功能是否启用
        if not await self.is_dingtalk_login_enabled(tenant_id):
            raise Exception("钉钉免密登录功能未启用")
        
        # 2. 通过code获取钉钉用户信息
        dingtalk_user = await self.get_user_info_by_code(tenant_id, code)
        dingtalk_userid = dingtalk_user.get("userid")
        
        if not dingtalk_userid:
            raise Exception("无法获取钉钉用户ID")
        
        # 3. 根据dingtalk_id查找系统用户
        logger.info(f"开始查找用户，钉钉userid: {dingtalk_userid}")
        user = await self.user_service.get_by_dingtalk_id(dingtalk_userid)
        
        if not user:
            logger.info(f"通过dingtalk_id未找到用户，尝试手机号匹配")
            # 尝试通过手机号匹配
            user_detail = await self.get_user_detail(tenant_id, dingtalk_userid)
            mobile = user_detail.get("mobile")
            logger.info(f"获取到钉钉用户手机号: {mobile}")
            
            if mobile:
                user = await self.user_service.get_by_phone(mobile)
                if user:
                    # 绑定dingtalk_id
                    user.dingtalk_id = dingtalk_userid
                    await self.db.commit()
                    logger.info(f"通过手机号匹配成功，已绑定dingtalk_id: {dingtalk_userid}")
                else:
                    logger.warning(f"通过手机号 {mobile} 也未找到用户")
            else:
                logger.warning("无法获取钉钉用户手机号")
        
        if not user:
            logger.error(f"钉钉登录失败：dingtalk_userid={dingtalk_userid}, 未找到对应用户")
            raise Exception("未找到对应的系统用户，请联系管理员")
        
        if not user.is_active:
            raise Exception("用户已被禁用")
        
        # 4. 生成JWT Token
        access_token = create_access_token(subject=user.id)
        refresh_token = create_refresh_token(subject=user.id)
        
        # 5. 更新最后登录时间
        await self.user_service.update_last_login(user.id)
        
        logger.info(f"钉钉免密登录成功: user_id={user.id}, username={user.username}")
        
        return user, Token(
            access_token=access_token,
            refresh_token=refresh_token,
        )

    async def get_public_config(self, tenant_id: int) -> Dict[str, Any]:
        """
        获取钉钉公开配置（前端需要用于初始化JSDK）
        
        Args:
            tenant_id: 租户ID
            
        Returns:
            {corp_id, agent_id, enabled}
        """
        enabled = await self.is_dingtalk_login_enabled(tenant_id)
        
        if not enabled:
            return {
                "enabled": False,
                "corp_id": None,
                "agent_id": None,
            }
        
        config = await self.get_dingtalk_config(tenant_id)
        
        return {
            "enabled": True,
            "corp_id": config.get("corp_id"),
            "agent_id": config.get("agent_id"),
        }
