"""
认证服务模块
"""
import sys
import os

# 添加src目录到Python路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../..'))

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, or_
from datetime import datetime, timedelta
from typing import Optional, Dict, Any, Tuple
from app.models import User, LoginLog, OAuthAccessToken, TokenBlacklist
from app.schemas.auth import LoginRequest, TokenResponse, UserInfoResponse
from app.utils.security import (
    verify_password, 
    create_access_token, 
    create_refresh_token,
    verify_token,
    generate_random_string
)
from app.core.config import config_handler
from app.core.exceptions import AuthenticationError, ValidationError
import ipaddress
import logging
logger = logging.getLogger(__name__)


class AuthService:
    """认证服务类"""
    
    def __init__(self, db: AsyncSession):
        self.db = db
    
    async def authenticate_user(
        self, 
        username: str, 
        password: str,
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None
    ) -> Tuple[User, TokenResponse]:
        """用户认证"""
        
        # 查找用户
        stmt = select(User).where(
            and_(
                or_(User.username == username, User.email == username),
                User.is_deleted == False
            )
        )
        logger.info(f"username={username}，password={password}")
        result = await self.db.execute(stmt)
        
        user = result.scalar_one_or_none()
        logger.info(f"user={user}")
        # 记录登录日志
        login_log = LoginLog(
            user_id=user.id if user else None,
            username=username,
            login_type="password",
            ip_address=ip_address,
            user_agent=user_agent,
            success=False
        )
        
        if not user:
            login_log.failure_reason = "用户不存在"
            self.db.add(login_log)
            await self.db.commit()
            raise AuthenticationError("用户名或密码错误")
        
        # 检查用户状态
        if not user.is_active:
            login_log.failure_reason = "用户已被禁用"
            self.db.add(login_log)
            await self.db.commit()
            raise AuthenticationError("用户已被禁用")
        
        # 检查账户锁定
        if user.locked_until and user.locked_until > datetime.utcnow():
            login_log.failure_reason = "账户已锁定"
            self.db.add(login_log)
            await self.db.commit()
            raise AuthenticationError(f"账户已锁定，请在{user.locked_until}后重试")
        
        # 验证密码
        if not verify_password(password, user.password_hash):
            # 增加失败次数
            user.failed_login_attempts += 1
            
            # 检查是否需要锁定账户
            if user.failed_login_attempts >= config_handler.get_int("admin_app", "MAX_LOGIN_ATTEMPTS", 5):
                user.locked_until = datetime.utcnow() + timedelta(
                    minutes=config_handler.get_int("admin_app", "LOCKOUT_DURATION_MINUTES", 30)
                )
                login_log.failure_reason = f"密码错误，账户已锁定{config_handler.get_int('admin_app', 'LOCKOUT_DURATION_MINUTES', 30)}分钟"
            else:
                remaining_attempts = config_handler.get_int("admin_app", "MAX_LOGIN_ATTEMPTS", 5) - user.failed_login_attempts
                login_log.failure_reason = f"密码错误，还有{remaining_attempts}次尝试机会"
            
            self.db.add(login_log)
            await self.db.commit()
            raise AuthenticationError("用户名或密码错误")
        
        # 登录成功，重置失败次数
        user.failed_login_attempts = 0
        user.locked_until = None
        user.last_login_at = datetime.utcnow()
        user.last_login_ip = ip_address
        
        # 生成令牌
        token_data = {
            "sub": user.id,
            "username": user.username,
            "email": user.email,
            "is_superuser": user.is_superuser
        }
        
        access_token = create_access_token(token_data)
        refresh_token = create_refresh_token({"sub": user.id})
        
        # 保存令牌到数据库
        oauth_token = OAuthAccessToken(
            user_id=user.id,
            app_id=None,  # 系统内部登录
            token=access_token,
            refresh_token=refresh_token,
            token_type="Bearer",
            scope="profile email",
            expires_at=datetime.utcnow() + timedelta(minutes=config_handler.get_int("admin_app", "ACCESS_TOKEN_EXPIRE_MINUTES", 30))
        )
        self.db.add(oauth_token)
        
        # 记录成功登录日志
        login_log.success = True
        login_log.user_id = user.id
        self.db.add(login_log)
        
        await self.db.commit()
        
        token_response = TokenResponse(
            access_token=access_token,
            refresh_token=refresh_token,
            token_type="Bearer",
            expires_in=config_handler.get_int("admin_app", "ACCESS_TOKEN_EXPIRE_MINUTES", 30) * 60,
            scope="profile email"
        )
        
        return user, token_response

    
    async def refresh_access_token(self, refresh_token: str) -> TokenResponse:
        """刷新访问令牌"""
        
        # 验证刷新令牌
        payload = verify_token(refresh_token)
        if not payload or payload.get("type") != "refresh":
            raise AuthenticationError("无效的刷新令牌")
        
        user_id = payload.get("sub")
        if not user_id:
            raise AuthenticationError("无效的刷新令牌")
        
        # 检查令牌是否在黑名单中
        stmt = select(TokenBlacklist).where(TokenBlacklist.token == refresh_token)
        result = await self.db.execute(stmt)
        if result.scalar_one_or_none():
            raise AuthenticationError("令牌已被撤销")
        
        # 查找用户
        stmt = select(User).where(and_(User.id == user_id, User.is_deleted == False))
        result = await self.db.execute(stmt)
        user = result.scalar_one_or_none()
        
        if not user or not user.is_active:
            raise AuthenticationError("用户不存在或已被禁用")
        
        # 生成新的访问令牌
        token_data = {
            "sub": user.id,
            "username": user.username,
            "email": user.email,
            "is_superuser": user.is_superuser
        }
        
        access_token = create_access_token(token_data)
        
        # 更新数据库中的令牌
        stmt = select(OAuthAccessToken).where(
            OAuthAccessToken.refresh_token == refresh_token
        )
        result = await self.db.execute(stmt)
        oauth_token = result.scalar_one_or_none()
        
        if oauth_token:
            oauth_token.token = access_token
            oauth_token.expires_at = datetime.utcnow() + timedelta(
                minutes=config_handler.get_int("admin_app", "ACCESS_TOKEN_EXPIRE_MINUTES", 30)
            )
            oauth_token.last_used_at = datetime.utcnow()
        
        await self.db.commit()
        
        return TokenResponse(
            access_token=access_token,
            refresh_token=refresh_token,
            token_type="Bearer",
            expires_in=config_handler.get_int("admin_app", "ACCESS_TOKEN_EXPIRE_MINUTES", 30) * 60,
            scope="profile email"
        )
    
    async def get_current_user(self, token: str) -> User:
        """获取当前用户"""
        
        # 验证令牌
        payload = verify_token(token)
        if not payload:
            raise AuthenticationError("无效的访问令牌")
        
        user_id = payload.get("sub")
        if not user_id:
            raise AuthenticationError("无效的访问令牌")
        
        # 检查令牌是否在黑名单中
        stmt = select(TokenBlacklist).where(TokenBlacklist.token == token)
        result = await self.db.execute(stmt)
        if result.scalar_one_or_none():
            raise AuthenticationError("令牌已被撤销")
        
        # 查找用户
        stmt = select(User).where(and_(User.id == user_id, User.is_deleted == False))
        result = await self.db.execute(stmt)
        user = result.scalar_one_or_none()
        
        if not user or not user.is_active:
            raise AuthenticationError("用户不存在或已被禁用")
        
        # 更新令牌最后使用时间
        stmt = select(OAuthAccessToken).where(OAuthAccessToken.token == token)
        result = await self.db.execute(stmt)
        oauth_token = result.scalar_one_or_none()
        if oauth_token:
            oauth_token.last_used_at = datetime.utcnow()
            await self.db.commit()
        
        return user
    
    async def logout(self, token: str, refresh_token: Optional[str] = None):
        """用户登出"""
        
        # 将访问令牌加入黑名单
        if token:
            payload = verify_token(token)
            if payload:
                exp = payload.get("exp")
                if exp:
                    expires_at = datetime.fromtimestamp(exp)
                    blacklist_token = TokenBlacklist(
                        token=token,
                        token_type="access_token",
                        expires_at=expires_at,
                        reason="用户登出"
                    )
                    self.db.add(blacklist_token)
        
        # 将刷新令牌加入黑名单
        if refresh_token:
            payload = verify_token(refresh_token)
            if payload:
                exp = payload.get("exp")
                if exp:
                    expires_at = datetime.fromtimestamp(exp)
                    blacklist_token = TokenBlacklist(
                        token=refresh_token,
                        token_type="refresh_token",
                        expires_at=expires_at,
                        reason="用户登出"
                    )
                    self.db.add(blacklist_token)
        
        await self.db.commit()
    
    async def get_user_info(self, user: User) -> UserInfoResponse:
        """获取用户信息"""
        
        # TODO: 获取用户角色和权限
        roles = []
        permissions = []
        
        return UserInfoResponse(
            id=user.id,
            username=user.username,
            email=user.email,
            phone=user.phone,
            avatar_url=user.avatar_url,
            is_active=user.is_active,
            roles=roles,
            permissions=permissions
        )
    
    async def validate_ip_address(self, ip_address: str) -> bool:
        """验证IP地址"""
        try:
            ipaddress.ip_address(ip_address)
            return True
        except ValueError:
            return False