"""
认证 API 路由

提供用户登录、注册、获取当前用户信息等接口。
"""

import logging
import io
import uuid
from typing import Optional
from datetime import datetime

from fastapi import APIRouter, status, Depends, UploadFile, File, Request
from pydantic import BaseModel, EmailStr, Field
from PIL import Image

from src.auth.jwt import (
    create_token_pair,
    verify_password,
    get_password_hash,
    verify_token,
    ACCESS_TOKEN_EXPIRE_MINUTES,
)
from src.services.token_blacklist_service import token_blacklist_service
from src.services.session_service import session_service
from src.services.email_service import email_service
from src.config.settings import REDIS_ENABLED, NODE_ENV
from src.auth.dependencies import get_current_user, UserAuth
from src.auth.models import RefreshTokenRequest
from src.services.user_service import user_service
from src.services.storage_service import storage_manager
from src.models.schemas import ResponseModel
from src.utils.errors import ErrorCode, AppException

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/auth", tags=["认证"])


def _build_user_payload(user: UserAuth) -> dict:
    return {
        "id": user.id,
        "username": user.username,
        "email": user.email,
        "is_active": user.is_active,
        "is_superuser": user.is_superuser,
    }


def _extract_bearer_token(request: Request) -> Optional[str]:
    auth_header = request.headers.get("Authorization", "")
    if auth_header.startswith("Bearer "):
        return auth_header[7:]
    return None


async def _get_access_payload(request: Request):
    access_token = _extract_bearer_token(request)
    if not access_token:
        return None
    return await verify_token(access_token, token_type="access")


def _serialize_session(session_db, current_session_id: Optional[str] = None) -> dict:
    return {
        "id": session_db.id,
        "session_family_id": session_db.session_family_id,
        "status": session_db.status,
        "device_name": session_db.device_name,
        "ip_address": session_db.ip_address,
        "user_agent": session_db.user_agent,
        "created_at": session_db.created_at,
        "updated_at": session_db.updated_at,
        "expires_at": session_db.expires_at,
        "last_used_at": session_db.last_used_at,
        "revoked_at": session_db.revoked_at,
        "revoked_reason": session_db.revoked_reason,
        "current": session_db.id == current_session_id,
    }


class LoginRequest(BaseModel):
    """登录请求"""

    username: str = Field(..., description="用户名或邮箱")
    password: str = Field(..., description="密码")


class RegisterRequest(BaseModel):
    """注册请求"""

    username: str = Field(..., min_length=3, max_length=50, description="用户名")
    email: EmailStr = Field(..., description="邮箱")
    password: str = Field(..., min_length=6, description="密码")


class TokenResponse(BaseModel):
    """Token 响应"""

    access_token: str
    refresh_token: str
    token_type: str = "bearer"
    expires_in: int = ACCESS_TOKEN_EXPIRE_MINUTES * 60  # 从配置读取


class UserInfoResponse(BaseModel):
    """用户信息响应"""

    id: str
    username: str
    email: Optional[str]
    is_active: bool
    is_superuser: bool


@router.post("/login", response_model=ResponseModel)
async def login(request: LoginRequest, http_request: Request):
    """
    用户登录

    支持使用用户名或邮箱登录。
    成功返回 access_token 和 refresh_token。
    """
    try:
        # 使用 authenticate_user 方法验证用户（包含密码验证）
        user = await user_service.authenticate_user(request.username, request.password)

        if not user:
            raise AppException(
                code=ErrorCode.UNAUTHORIZED,
                message="Invalid credentials",
                status_code=401,
                details={"headers": {"WWW-Authenticate": "Bearer"}}
            )

        # 检查用户是否激活
        if not user.is_active:
            raise AppException(
                code=ErrorCode.FORBIDDEN,
                message="User account is inactive",
                status_code=403
            )

        # 更新最后登录时间
        await user_service.update_last_login(user.id)

        session_id = str(uuid.uuid4())
        session_family_id = str(uuid.uuid4())
        tokens = create_token_pair(
            user_id=user.id,
            scopes=["user"],
            session_id=session_id,
            session_family_id=session_family_id,
        )
        session_service.create_session(
            user_id=user.id,
            refresh_token=tokens.refresh_token,
            session_id=session_id,
            session_family_id=session_family_id,
            ip_address=http_request.client.host if http_request.client else None,
            user_agent=http_request.headers.get("user-agent"),
        )

        logger.info(f"User logged in: {user.username} ({user.id})")

        return ResponseModel(
            data={
                "access_token": tokens.access_token,
                "refresh_token": tokens.refresh_token,
                "token_type": "bearer",
                "expires_in": ACCESS_TOKEN_EXPIRE_MINUTES * 60,
                "session_id": session_id,
                "user": _build_user_payload(user),
            }
        )

    except AppException:
        raise
    except Exception as e:
        logger.error(f"Login error: {e}")
        raise AppException(
            code=ErrorCode.INTERNAL_ERROR,
            message="Internal server error",
            status_code=500
        )


@router.post("/register", response_model=ResponseModel)
async def register(request: RegisterRequest, http_request: Request):
    """
    用户注册

    创建新用户账号，用户名和邮箱必须唯一。
    """
    try:
        # 检查用户名是否已存在
        existing_user = await user_service.get_user_by_username(request.username)
        if existing_user:
            raise AppException(
                code=ErrorCode.CONFLICT,
                message="Username already registered",
                status_code=400
            )

        # 检查邮箱是否已存在
        existing_email = await user_service.get_user_by_email(request.email)
        if existing_email:
            raise AppException(
                code=ErrorCode.CONFLICT,
                message="Email already registered",
                status_code=400
            )

        # 创建用户
        user = await user_service.create_user(
            username=request.username, email=request.email, password=request.password
        )

        session_id = str(uuid.uuid4())
        session_family_id = str(uuid.uuid4())
        tokens = create_token_pair(
            user_id=user.id,
            scopes=["user"],
            session_id=session_id,
            session_family_id=session_family_id,
        )
        session_service.create_session(
            user_id=user.id,
            refresh_token=tokens.refresh_token,
            session_id=session_id,
            session_family_id=session_family_id,
            ip_address=http_request.client.host if http_request.client else None,
            user_agent=http_request.headers.get("user-agent"),
        )

        logger.info(f"User registered: {user.username} ({user.id})")

        return ResponseModel(
            data={
                "access_token": tokens.access_token,
                "refresh_token": tokens.refresh_token,
                "token_type": "bearer",
                "expires_in": ACCESS_TOKEN_EXPIRE_MINUTES * 60,
                "session_id": session_id,
                "user": _build_user_payload(user),
            }
        )

    except AppException:
        raise
    except Exception as e:
        logger.error(f"Registration error: {e}")
        raise AppException(
            code=ErrorCode.INTERNAL_ERROR,
            message="Internal server error",
            status_code=500
        )


@router.post("/refresh", response_model=ResponseModel)
async def refresh_token(request: RefreshTokenRequest, http_request: Request):
    """
    刷新 access token

    使用 refresh token 获取新的 access token。
    """
    try:
        # 验证 refresh token（检查黑名单）
        payload = await verify_token(request.refresh_token, token_type="refresh")
        if not payload:
            raise AppException(
                code=ErrorCode.UNAUTHORIZED,
                message="Invalid or revoked refresh token",
                status_code=401,
                details={"headers": {"WWW-Authenticate": "Bearer"}}
            )

        if not payload.sid:
            raise AppException(
                code=ErrorCode.UNAUTHORIZED,
                message="Refresh token session is missing",
                status_code=401,
                details={"headers": {"WWW-Authenticate": "Bearer"}}
            )

        user = await user_service.get_user_by_id(payload.sub)
        if not user or not user.is_active:
            if user:
                session_service.revoke_user_sessions(user.id, reason="inactive_user")
            raise AppException(
                code=ErrorCode.UNAUTHORIZED,
                message="User session is no longer active",
                status_code=401,
                details={"headers": {"WWW-Authenticate": "Bearer"}}
            )

        new_session_id = str(uuid.uuid4())
        tokens = create_token_pair(
            user_id=payload.sub,
            scopes=payload.scopes or ["user"],
            session_id=new_session_id,
            session_family_id=payload.sfid,
        )

        rotated_session = session_service.rotate_refresh_token(
            payload.sid,
            request.refresh_token,
            tokens.refresh_token,
            new_session_id=new_session_id,
            ip_address=http_request.client.host if http_request.client else None,
            user_agent=http_request.headers.get("user-agent"),
        )
        if not rotated_session:
            raise AppException(
                code=ErrorCode.UNAUTHORIZED,
                message="Invalid or replayed refresh token",
                status_code=401,
                details={"headers": {"WWW-Authenticate": "Bearer"}}
            )

        user_data = None
        if user:
            user_data = _build_user_payload(user)

        return ResponseModel(
            data={
                "access_token": tokens.access_token,
                "refresh_token": tokens.refresh_token,
                "token_type": "bearer",
                "expires_in": ACCESS_TOKEN_EXPIRE_MINUTES * 60,
                "session_id": rotated_session.id,
                **({"user": user_data} if user_data else {}),
            }
        )

    except AppException:
        raise
    except Exception as e:
        logger.error(f"Token refresh error: {e}")
        raise AppException(
            code=ErrorCode.INTERNAL_ERROR,
            message="Internal server error",
            status_code=500
        )


@router.get("/me", response_model=ResponseModel)
async def get_me(current_user: UserAuth = Depends(get_current_user)):
    """
    获取当前登录用户信息

    需要有效的 access token。
    """
    return ResponseModel(
        data={
            "id": current_user.id,
            "username": current_user.username,
            "email": current_user.email,
            "avatar_url": current_user.avatar_url,
            "is_active": current_user.is_active,
            "is_superuser": current_user.is_superuser,
            "permissions": current_user.permissions,
            "roles": current_user.roles,
        }
    )


@router.get("/session", response_model=ResponseModel)
async def get_current_session(
    http_request: Request,
    current_user: UserAuth = Depends(get_current_user),
):
    payload = await _get_access_payload(http_request)
    session_data = None

    if payload and payload.sid:
        session_db = session_service.get_session(payload.sid)
        if session_db and session_db.user_id == current_user.id:
            session_data = _serialize_session(session_db, current_session_id=payload.sid)

    return ResponseModel(
        data={
            "authenticated": True,
            "session_id": payload.sid if payload else None,
            "user": {
                "id": current_user.id,
                "username": current_user.username,
                "email": current_user.email,
                "avatar_url": current_user.avatar_url,
                "is_active": current_user.is_active,
                "is_superuser": current_user.is_superuser,
                "permissions": current_user.permissions,
                "roles": current_user.roles,
            },
            "session": session_data,
        }
    )


@router.get("/sessions", response_model=ResponseModel)
async def list_sessions(
    http_request: Request,
    include_inactive: bool = False,
    current_user: UserAuth = Depends(get_current_user),
):
    payload = await _get_access_payload(http_request)
    current_session_id = payload.sid if payload else None
    sessions = session_service.list_user_sessions(
        current_user.id,
        include_inactive=include_inactive,
    )

    return ResponseModel(
        data={
            "items": [
                _serialize_session(session_db, current_session_id=current_session_id)
                for session_db in sessions
            ],
            "total": len(sessions),
        }
    )


@router.delete("/sessions/{session_id}", response_model=ResponseModel)
async def revoke_session(
    session_id: str,
    http_request: Request,
    current_user: UserAuth = Depends(get_current_user),
):
    payload = await _get_access_payload(http_request)
    revoked = session_service.revoke_user_session(
        current_user.id,
        session_id,
        reason="user_revoke",
    )

    if not revoked:
        raise AppException(
            code=ErrorCode.NOT_FOUND,
            message="Session not found",
            status_code=404,
        )

    revoked_access = False
    if payload and payload.sid == session_id:
        access_token = _extract_bearer_token(http_request)
        if access_token:
            revoked_access = await token_blacklist_service.revoke_token(
                access_token,
                reason="user_revoke",
            )

    return ResponseModel(
        message="Session revoked successfully",
        data={
            "session_id": session_id,
            "revoked": True,
            "current": payload.sid == session_id if payload else False,
            "revoked_access": revoked_access,
        }
    )


@router.post("/logout", response_model=ResponseModel)
async def logout(
    request: RefreshTokenRequest,
    http_request: Request,
    current_user: UserAuth = Depends(get_current_user)
):
    """
    用户登出

    将当前 access token 和 refresh token 加入黑名单，使其失效。
    客户端也需要清除本地存储的 token。
    """
    try:
        access_token = _extract_bearer_token(http_request)
        revoked_access = False
        if access_token:
            revoked_access = await token_blacklist_service.revoke_token(
                access_token,
                reason="logout",
            )

        # 撤销 refresh token（如果提供）
        revoked_refresh = False
        if request.refresh_token:
            refresh_payload = await verify_token(request.refresh_token, token_type="refresh")
            if refresh_payload and refresh_payload.sid:
                session_service.revoke_session(refresh_payload.sid, reason="logout")
            await token_blacklist_service.revoke_token(
                request.refresh_token,
                reason="logout"
            )
            revoked_refresh = True

        logger.info(f"User logged out: {current_user.username} ({current_user.id})")

        return ResponseModel(
            message="Logged out successfully",
            data={
                "user_id": current_user.id,
                "revoked": revoked_access or revoked_refresh,
                "revoked_access": revoked_access,
                "revoked_refresh": revoked_refresh,
            }
        )
    except Exception as e:
        logger.error(f"Logout error: {e}")
        # 即使撤销失败也返回成功，因为客户端应该清除本地 token
        return ResponseModel(message="Logged out successfully")


class LogoutAllRequest(BaseModel):
    """登出所有设备请求"""
    pass


@router.post("/logout-all", response_model=ResponseModel)
async def logout_all_devices(
    request: LogoutAllRequest,
    current_user: UserAuth = Depends(get_current_user)
):
    """
    从所有设备登出

    撤销该用户的所有 Token，使用户在所有设备上登出。
    """
    try:
        # 撤销该用户所有 token
        await token_blacklist_service.revoke_all_user_tokens(
            current_user.id,
            reason="logout_all"
        )
        revoked_sessions = session_service.revoke_user_sessions(
            current_user.id,
            reason="logout_all",
        )

        logger.info(f"User logged out from all devices: {current_user.username} ({current_user.id})")

        return ResponseModel(
            message="Logged out from all devices successfully",
            data={
                "user_id": current_user.id,
                "all_devices": True,
                "revoked_sessions": revoked_sessions,
            }
        )
    except Exception as e:
        logger.error(f"Logout all error: {e}")
        raise AppException(
            code=ErrorCode.INTERNAL_ERROR,
            message="Failed to logout from all devices",
            status_code=500
        )


@router.post("/avatar", response_model=ResponseModel)
async def upload_avatar(
    file: UploadFile = File(...),
    current_user: UserAuth = Depends(get_current_user)
):
    """
    上传用户头像

    支持 JPG、PNG 格式，最大 5MB。
    头像会被裁剪为正方形并压缩至 256x256 像素。
    """
    try:
        # 验证文件类型
        allowed_types = {'image/jpeg', 'image/png', 'image/jpg'}
        if file.content_type not in allowed_types:
            raise AppException(
                message="Only JPG and PNG images are allowed",
                code=ErrorCode.INVALID_PARAMETER,
                status_code=400
            )

        # 读取文件内容
        contents = await file.read()
        max_size = 5 * 1024 * 1024  # 5MB
        if len(contents) > max_size:
            raise AppException(
                message="File size exceeds 5MB limit",
                code=ErrorCode.INVALID_PARAMETER,
                status_code=400
            )

        # 使用 PIL 处理图片
        image = Image.open(io.BytesIO(contents))

        # 转换为 RGB（处理RGBA图片）
        if image.mode in ('RGBA', 'LA', 'P'):
            background = Image.new('RGB', image.size, (255, 255, 255))
            if image.mode == 'P':
                image = image.convert('RGBA')
            if image.mode in ('RGBA', 'LA'):
                background.paste(image, mask=image.split()[-1] if image.mode in ('RGBA', 'LA') else None)
                image = background

        # 裁剪为正方形（从中心）
        width, height = image.size
        min_dim = min(width, height)
        left = (width - min_dim) // 2
        top = (height - min_dim) // 2
        right = left + min_dim
        bottom = top + min_dim
        image = image.crop((left, top, right, bottom))

        # 调整大小为 256x256
        image = image.resize((256, 256), Image.Resampling.LANCZOS)

        # 保存为 PNG
        output = io.BytesIO()
        image.save(output, format='PNG', optimize=True)
        output.seek(0)

        # 生成存储路径: avatars/{user_id}/{timestamp}.png
        timestamp = int(datetime.now().timestamp())
        storage_path = f"avatars/{current_user.id}/{timestamp}.png"

        # 上传到存储
        avatar_url = storage_manager.save(storage_path, output.getvalue())

        # 更新用户头像 URL
        updated_user = await user_service.update_user(
            current_user.id,
            avatar_url=avatar_url
        )

        if not updated_user:
            raise AppException(
                message="Failed to update user avatar",
                code=ErrorCode.INTERNAL_ERROR,
                status_code=500
            )

        logger.info(f"Avatar uploaded for user {current_user.username}: {avatar_url}")

        return ResponseModel(
            data={
                "avatar_url": avatar_url,
                "user": {
                    "id": updated_user.id,
                    "username": updated_user.username,
                    "email": updated_user.email,
                    "avatar_url": updated_user.avatar_url,
                    "is_active": updated_user.is_active,
                    "is_superuser": updated_user.is_superuser,
                }
            }
        )

    except AppException:
        raise
    except Exception as e:
        logger.error(f"Avatar upload error: {e}")
        raise AppException(
            message=f"Failed to upload avatar: {str(e)}",
            code=ErrorCode.INTERNAL_ERROR,
            status_code=500
        )


# ===== Password Reset Models =====

class ForgotPasswordRequest(BaseModel):
    """忘记密码请求"""
    email: EmailStr = Field(..., description="用户邮箱")


class ResetPasswordRequest(BaseModel):
    """重置密码请求"""
    token: str = Field(..., description="重置令牌")
    new_password: str = Field(..., min_length=6, description="新密码")


class VerifyResetTokenRequest(BaseModel):
    """验证重置令牌请求"""
    token: str = Field(..., description="重置令牌")


# Password reset token storage (using Redis if available, else memory)
_reset_tokens = {}  # In-memory fallback {token: {"user_id": str, "expires": float}}


async def _store_reset_token(token: str, user_id: str, expires_in: int = 3600) -> None:
    """Store reset token in Redis or memory"""
    if REDIS_ENABLED:
        try:
            import redis.asyncio as aioredis
            from src.config.settings import REDIS_URL
            redis_client = await aioredis.from_url(REDIS_URL, encoding="utf-8", decode_responses=True)
            await redis_client.setex(f"pwd_reset:{token}", expires_in, user_id)
            await redis_client.close()
            return
        except Exception as e:
            logger.warning(f"Redis not available for reset token, using memory: {e}")

    # Fallback to memory
    _reset_tokens[token] = {
        "user_id": user_id,
        "expires": datetime.now().timestamp() + expires_in
    }


async def _get_reset_token_user_id(token: str) -> Optional[str]:
    """Get user_id from reset token"""
    if REDIS_ENABLED:
        try:
            import redis.asyncio as aioredis
            from src.config.settings import REDIS_URL
            redis_client = await aioredis.from_url(REDIS_URL, encoding="utf-8", decode_responses=True)
            user_id = await redis_client.get(f"pwd_reset:{token}")
            await redis_client.close()
            return user_id
        except Exception:
            pass

    # Fallback to memory
    token_data = _reset_tokens.get(token)
    if token_data:
        if token_data["expires"] > datetime.now().timestamp():
            return token_data["user_id"]
        else:
            # Expired, remove it
            del _reset_tokens[token]
    return None


async def _delete_reset_token(token: str) -> None:
    """Delete reset token"""
    if REDIS_ENABLED:
        try:
            import redis.asyncio as aioredis
            from src.config.settings import REDIS_URL
            redis_client = await aioredis.from_url(REDIS_URL, encoding="utf-8", decode_responses=True)
            await redis_client.delete(f"pwd_reset:{token}")
            await redis_client.close()
            return
        except Exception:
            pass

    # Fallback to memory
    if token in _reset_tokens:
        del _reset_tokens[token]


def _generate_reset_token() -> str:
    """Generate secure reset token"""
    import secrets
    return secrets.token_urlsafe(32)


@router.post("/forgot-password", response_model=ResponseModel)
async def forgot_password(request: ForgotPasswordRequest):
    """
    请求密码重置

    发送密码重置邮件。
    令牌有效期为 1 小时。
    """
    try:
        # 查找用户
        user = await user_service.get_user_by_email(request.email)
        if not user:
            # 为了安全，即使用户不存在也返回成功，不暴露邮箱是否存在
            logger.info(f"Password reset requested for non-existent email: {request.email}")
            return ResponseModel(
                message="If the email exists, a reset link has been sent"
            )

        # 生成重置令牌
        token = _generate_reset_token()

        # 存储令牌（1小时有效）
        await _store_reset_token(token, user.id, expires_in=3600)

        # 发送重置邮件
        email_result = await email_service.send_password_reset(
            to_email=user.email,
            username=user.username,
            reset_token=token,
            expires_in=1
        )

        if email_result["success"]:
            logger.info(f"Password reset email sent to {user.email}")
            return ResponseModel(
                message="Password reset email sent"
            )
        else:
            # 邮件发送失败，但在开发环境可以返回令牌
            logger.warning(f"Failed to send reset email: {email_result.get('error')}")

            # 生产环境不返回令牌
            if NODE_ENV == "production":
                return ResponseModel(
                    message="Failed to send reset email. Please try again later."
                )
            else:
                # 开发环境返回令牌以便测试
                return ResponseModel(
                    data={
                        "message": "Password reset email sent",
                        "reset_token": token,  # 开发环境返回，生产环境不应返回
                        "expires_in": 3600,
                        "email_error": email_result.get("error")
                    }
                )

    except Exception as e:
        logger.error(f"Forgot password error: {e}")
        raise AppException(
                message="Failed to process password reset request",
                code=ErrorCode.INTERNAL_ERROR,
                status_code=500
            )


@router.post("/verify-reset-token", response_model=ResponseModel)
async def verify_reset_token(request: VerifyResetTokenRequest):
    """
    验证密码重置令牌

    检查令牌是否有效，用于前端重置密码页面验证。
    """
    try:
        user_id = await _get_reset_token_user_id(request.token)

        if not user_id:
            raise AppException(
                message="Invalid or expired reset token",
                code=ErrorCode.INVALID_PARAMETER,
                status_code=400
            )

        # 获取用户信息
        user = await user_service.get_user_by_id(user_id)
        if not user:
            raise AppException(
                message="User not found",
                code=ErrorCode.INVALID_PARAMETER,
                status_code=400
            )

        return ResponseModel(
            data={
                "valid": True,
                "user_id": user_id,
                "email": user.email
            }
        )

    except AppException:
        raise
    except Exception as e:
        logger.error(f"Verify reset token error: {e}")
        raise AppException(
                message="Failed to verify reset token",
                code=ErrorCode.INTERNAL_ERROR,
                status_code=500
            )


@router.post("/reset-password", response_model=ResponseModel)
async def reset_password(request: ResetPasswordRequest):
    """
    重置密码

    使用有效的重置令牌设置新密码，并撤销该用户所有 Token。
    """
    try:
        # 验证令牌
        user_id = await _get_reset_token_user_id(request.token)

        if not user_id:
            raise AppException(
                message="Invalid or expired reset token",
                code=ErrorCode.INVALID_PARAMETER,
                status_code=400
            )

        # 更新密码
        updated_user = await user_service.update_user(
            user_id,
            password=request.new_password
        )

        if not updated_user:
            raise AppException(
                message="User not found",
                code=ErrorCode.INVALID_PARAMETER,
                status_code=400
            )

        # 删除已使用的令牌
        await _delete_reset_token(request.token)

        # 撤销该用户所有 Token（强制重新登录）
        await token_blacklist_service.revoke_all_user_tokens(
            user_id,
            reason="password_reset"
        )

        # 使该用户所有缓存失效
        await user_service.invalidate_user_cache(user_id)

        logger.info(f"Password reset successful for user {updated_user.username}")

        return ResponseModel(
            message="Password reset successful",
            data={
                "user_id": updated_user.id,
                "email": updated_user.email
            }
        )

    except AppException:
        raise
    except Exception as e:
        logger.error(f"Reset password error: {e}")
        raise AppException(
                message="Failed to reset password",
                code=ErrorCode.INTERNAL_ERROR,
                status_code=500
            )
