"""
认证和授权依赖项 - 使用 JWT (JSON Web Token)
支持 HttpOnly Cookie 和 Authorization Header 双模式认证
"""
import os
import logging
from typing import Optional
from datetime import datetime, timedelta
from fastapi import Depends, HTTPException, status, Request, Cookie
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
import bcrypt
from jose import jwt, JWTError

from app.core.database import get_db
from app.models.users import User, UserRole


logger = logging.getLogger(__name__)

# 环境判断
IS_PRODUCTION = os.environ.get("ENV", "development").lower() == "production"

# JWT 配置
# 生产环境必须配置 JWT_SECRET_KEY 环境变量
_jwt_secret = os.environ.get("JWT_SECRET_KEY")
if IS_PRODUCTION and not _jwt_secret:
    raise ValueError(
        "CRITICAL SECURITY ERROR: JWT_SECRET_KEY environment variable is required in production! "
        "Generate one with: python -c \"import secrets; print(secrets.token_urlsafe(32))\""
    )
SECRET_KEY = _jwt_secret or "dev-only-insecure-key-not-for-production"
if not _jwt_secret:
    logger.warning("JWT_SECRET_KEY not configured - using insecure default. DO NOT USE IN PRODUCTION!")

ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_HOURS = 24 * 7  # Token 有效期 7 天（内部系统延长有效期减少登录频率）

# Cookie 配置
COOKIE_NAME = "auth_token"
COOKIE_MAX_AGE = ACCESS_TOKEN_EXPIRE_HOURS * 3600  # 秒
COOKIE_SECURE = IS_PRODUCTION  # 生产环境强制使用 HTTPS（静态配置，仅作参考）
COOKIE_SAMESITE = "lax"  # 防止 CSRF


def get_cookie_secure(request: Request) -> bool:
    """
    根据请求协议动态决定是否使用 secure 标志

    解决问题：局域网 HTTP 访问时，Cookie secure=true 导致浏览器拒绝设置/删除 Cookie

    判断逻辑：
    1. 检查 X-Forwarded-Proto 头（nginx 反向代理场景）
    2. 检查请求 URL scheme

    Returns:
        bool: True 表示使用 HTTPS，Cookie 应设置 secure=True
    """
    # 检查 X-Forwarded-Proto 头（nginx 反向代理会设置此头）
    forwarded_proto = request.headers.get("x-forwarded-proto", "")
    if forwarded_proto.lower() == "https":
        return True

    # 直接检查请求 URL scheme
    if request.url.scheme == "https":
        return True

    # HTTP 请求，不使用 secure 标志
    logger.debug(f"HTTP request detected (scheme={request.url.scheme}), Cookie secure=False")
    return False

# Bearer token认证 (可选，用于 API 客户端)
security = HTTPBearer(auto_error=False)  # 不自动报错，允许 cookie 认证


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """验证密码 - 使用bcrypt直接验证"""
    try:
        return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
    except Exception as e:
        logger.error(f"Password verification error: {e}")
        return False


def get_password_hash(password: str) -> str:
    """获取密码哈希值 - 使用bcrypt直接生成"""
    salt = bcrypt.gensalt()
    return bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')


def create_access_token(user_id: int, username: str, role: str) -> str:
    """
    创建 JWT 访问令牌

    Token payload 包含:
    - sub: 用户ID
    - username: 用户名
    - role: 用户角色
    - exp: 过期时间
    """
    expire = datetime.utcnow() + timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS)
    payload = {
        "sub": str(user_id),
        "username": username,
        "role": role,
        "exp": expire
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
    logger.info(f"Created JWT token for user {username} (expires: {expire})")
    return token


def decode_access_token(token: str) -> Optional[dict]:
    """
    解码并验证 JWT token

    Returns:
        解码后的 payload，如果无效则返回 None
    """
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except JWTError as e:
        logger.warning(f"JWT decode error: {e}")
        return None


def invalidate_token(token: str):
    """
    使令牌失效

    注意：JWT 是无状态的，真正的"注销"需要实现黑名单机制
    当前实现依赖前端清除 localStorage，实际注销效果由 token 过期实现
    """
    # JWT 无状态，此函数保留用于 API 兼容性
    # 前端会清除 localStorage 中的 token
    logger.info("Token invalidation requested (JWT is stateless, client should clear token)")
    pass


async def get_current_user(
    request: Request,
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
    auth_token: Optional[str] = Cookie(default=None, alias=COOKIE_NAME),
    db: AsyncSession = Depends(get_db)
) -> User:
    """
    获取当前用户

    支持两种认证方式（优先级从高到低）：
    1. Authorization Header (Bearer token) - 用于 API 客户端
    2. HttpOnly Cookie - 用于浏览器（更安全）

    通过解码 JWT token 获取用户信息，然后从数据库查询完整用户数据
    """
    token = None

    # 优先使用 Authorization Header
    if credentials and credentials.credentials:
        token = credentials.credentials
        logger.debug("Using token from Authorization header")
    # 其次使用 Cookie
    elif auth_token:
        token = auth_token
        logger.debug("Using token from HttpOnly cookie")

    if not token:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Not authenticated",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 解码 JWT token
    payload = decode_access_token(token)
    if payload is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or expired token",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 从 payload 获取用户ID
    user_id = payload.get("sub")
    if user_id is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token payload",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 查询用户
    try:
        stmt = select(User).where(User.id == int(user_id))
        result = await db.execute(stmt)
        user = result.scalar_one_or_none()
    except Exception as e:
        logger.error(f"Database error while fetching user: {e}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Database error"
        )

    if not user or not user.is_active:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="User not found or inactive",
            headers={"WWW-Authenticate": "Bearer"},
        )

    return user


async def get_current_user_optional(
    request: Request,
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
    auth_token: Optional[str] = Cookie(default=None, alias=COOKIE_NAME),
    db: AsyncSession = Depends(get_db)
) -> Optional[User]:
    """
    获取当前用户（可选，不抛出异常）

    用于不强制要求认证的端点，如登出接口。
    如果认证失败，返回 None 而不是抛出异常。
    """
    try:
        return await get_current_user(request, credentials, auth_token, db)
    except HTTPException:
        return None


async def get_current_active_user(
    current_user: User = Depends(get_current_user)
) -> User:
    """获取当前活跃用户"""
    if not current_user.is_active:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


async def get_current_user_from_query_token(
    request: Request,
    token: Optional[str] = None,
    db: AsyncSession = Depends(get_db)
) -> User:
    """
    从 URL 查询参数获取当前用户

    用于浏览器原生元素（如 <video>、<img>）无法设置 Authorization header 的场景。
    token 可以通过查询参数 ?token=xxx 传递。

    注意：这种方式安全性较低，token 会出现在 URL 和服务器日志中。
    仅用于视频流等无法使用 header 认证的场景。
    """
    if not token:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Not authenticated",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 解码 JWT token
    payload = decode_access_token(token)
    if payload is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or expired token",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 从 payload 获取用户ID
    user_id = payload.get("sub")
    if user_id is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token payload",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 查询用户
    try:
        stmt = select(User).where(User.id == int(user_id))
        result = await db.execute(stmt)
        user = result.scalar_one_or_none()
    except Exception as e:
        logger.error(f"Database error while fetching user: {e}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Database error"
        )

    if not user or not user.is_active:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="User not found or inactive",
            headers={"WWW-Authenticate": "Bearer"},
        )

    return user


async def require_admin(
    current_user: User = Depends(get_current_active_user)
) -> User:
    """需要管理员权限"""
    if current_user.role != UserRole.ADMIN:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Not enough permissions"
        )
    return current_user


async def require_modify_permission(
    current_user: User = Depends(get_current_active_user)
) -> User:
    """需要修改权限（管理员才能修改）"""
    if not current_user.can_modify():
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You don't have permission to modify data"
        )
    return current_user


# 初始化默认用户数据（用于迁移后的初始化）
async def init_users(db: AsyncSession):
    """
    初始化默认用户

    安全说明：
    - 生产环境必须通过环境变量配置初始密码
    - 开发环境使用默认密码以方便调试
    """
    # 检查是否已有用户
    stmt = select(User).limit(1)
    result = await db.execute(stmt)
    if result.scalar_one_or_none():
        return  # 已有用户，不需要初始化

    # 从环境变量获取初始密码，生产环境必须配置
    admin_password = os.environ.get("INIT_ADMIN_PASSWORD")
    readonly_password = os.environ.get("INIT_READONLY_PASSWORD")

    if IS_PRODUCTION:
        if not admin_password or not readonly_password:
            logger.error(
                "SECURITY ERROR: Initial passwords must be set via environment variables "
                "INIT_ADMIN_PASSWORD and INIT_READONLY_PASSWORD in production!"
            )
            raise ValueError(
                "Initial passwords not configured. Set INIT_ADMIN_PASSWORD and INIT_READONLY_PASSWORD environment variables."
            )
    else:
        # 开发环境使用默认密码
        admin_password = admin_password or "dev-admin-password"
        readonly_password = readonly_password or "dev-readonly-password"
        logger.warning("Using default development passwords. DO NOT USE IN PRODUCTION!")

    # 创建管理员用户
    admin_user = User(
        username="admin",
        password_hash=get_password_hash(admin_password),
        role=UserRole.ADMIN,
        is_active=True
    )
    db.add(admin_user)
    logger.info("Created admin user")

    # 创建只读用户
    readonly_user = User(
        username="信泽客服",
        password_hash=get_password_hash(readonly_password),
        role=UserRole.READONLY,
        is_active=True
    )
    db.add(readonly_user)
    logger.info("Created readonly user")

    await db.commit()
    logger.info("Initial users created successfully")
