"""
Authentication Utilities

This module provides authentication middleware, decorators, and helper functions
for protecting routes and managing user sessions.

Usage:
    from app.utils.auth import require_auth, require_tier, get_current_user

    @app.route('/protected')
    @require_auth
    def protected_route():
        user = get_current_user()
        return f"Hello, {user.name}!"

    @app.route('/premium-feature')
    @require_auth
    @require_tier('premium')
    def premium_feature():
        return "Premium content"
"""

from functools import wraps
from typing import Optional, Callable
from flask import request, g, jsonify, redirect, url_for

from app.services.appwrite_service import AppwriteService, UserData
from app.utils.response import unauthorized_response, forbidden_response
from app.utils.logging import get_logger
from app.config import get_config
from appwrite.exception import AppwriteException


# Initialize logger
logger = get_logger(__file__)


def extract_session_token() -> Optional[str]:
    """
    Extract the session token from the request cookie.

    Returns:
        Session token string if found, None otherwise
    """
    config = get_config()
    cookie_name = config.auth.cookie_name

    token = request.cookies.get(cookie_name)
    return token


def verify_session(token: str) -> Optional[UserData]:
    """
    Verify a session token and return the associated user data.

    This function:
    1. Validates the session token with Appwrite
    2. Checks if the session is still active (not expired)
    3. Retrieves and returns the user data

    Args:
        token: Session token from cookie

    Returns:
        UserData object if session is valid, None otherwise
    """
    try:
        appwrite = AppwriteService()

        # Validate session
        session_data = appwrite.get_session(session_id=token)

        # Get user data
        user_data = appwrite.get_user(user_id=session_data.user_id)
        return user_data

    except AppwriteException as e:
        logger.warning("Session verification failed", error=str(e), code=e.code)
        return None
    except Exception as e:
        logger.error("Unexpected error during session verification", error=str(e))
        return None


def get_current_user() -> Optional[UserData]:
    """
    Get the current authenticated user from the request context.

    This function retrieves the user object that was attached to the
    request context by the @require_auth decorator.

    Returns:
        UserData object if user is authenticated, None otherwise

    Usage:
        @app.route('/profile')
        @require_auth
        def profile():
            user = get_current_user()
            return f"Welcome, {user.name}!"
    """
    return getattr(g, 'current_user', None)


def require_auth(f: Callable) -> Callable:
    """
    Decorator to require authentication for a route (API endpoints).

    This decorator:
    1. Extracts the session token from the cookie
    2. Verifies the session with Appwrite
    3. Attaches the user object to the request context (g.current_user)
    4. Allows the request to proceed if authenticated
    5. Returns 401 Unauthorized JSON if not authenticated

    For web views, use @require_auth_web instead.

    Args:
        f: The Flask route function to wrap

    Returns:
        Wrapped function with authentication check

    Usage:
        @app.route('/api/protected')
        @require_auth
        def protected_route():
            user = get_current_user()
            return f"Hello, {user.name}!"
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # Extract session token from cookie
        token = extract_session_token()

        if not token:
            logger.warning("Authentication required but no session token provided", path=request.path)
            return unauthorized_response(message="Authentication required. Please log in.")

        # Verify session and get user
        user = verify_session(token)

        if not user:
            logger.warning("Invalid or expired session token", path=request.path)
            return unauthorized_response(message="Session invalid or expired. Please log in again.")

        # Attach user to request context
        g.current_user = user

        logger.debug("User authenticated", user_id=user.id, email=user.email, path=request.path)

        # Call the original function
        return f(*args, **kwargs)

    return decorated_function


def require_auth_web(f: Callable) -> Callable:
    """
    Decorator to require authentication for a web view route.

    This decorator:
    1. Extracts the session token from the cookie
    2. Verifies the session with Appwrite
    3. Attaches the user object to the request context (g.current_user)
    4. Allows the request to proceed if authenticated
    5. Redirects to login page if not authenticated

    For API endpoints, use @require_auth instead.

    Args:
        f: The Flask route function to wrap

    Returns:
        Wrapped function with authentication check

    Usage:
        @app.route('/dashboard')
        @require_auth_web
        def dashboard():
            user = get_current_user()
            return render_template('dashboard.html', user=user)
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # Extract session token from cookie
        token = extract_session_token()

        if not token:
            logger.warning("Authentication required but no session token provided", path=request.path)
            return redirect(url_for('auth_views.login'))

        # Verify session and get user
        user = verify_session(token)

        if not user:
            logger.warning("Invalid or expired session token", path=request.path)
            return redirect(url_for('auth_views.login'))

        # Attach user to request context
        g.current_user = user

        logger.debug("User authenticated", user_id=user.id, email=user.email, path=request.path)

        # Call the original function
        return f(*args, **kwargs)

    return decorated_function


def require_tier(minimum_tier: str) -> Callable:
    """
    Decorator to require a minimum subscription tier for a route.

    This decorator must be used AFTER @require_auth.

    Tier hierarchy (from lowest to highest):
    - free
    - basic
    - premium
    - elite

    Args:
        minimum_tier: Minimum required tier (free, basic, premium, elite)

    Returns:
        Decorator function

    Raises:
        ValueError: If minimum_tier is invalid

    Usage:
        @app.route('/premium-feature')
        @require_auth
        @require_tier('premium')
        def premium_feature():
            return "Premium content"
    """
    # Define tier hierarchy
    tier_hierarchy = {
        'free': 0,
        'basic': 1,
        'premium': 2,
        'elite': 3
    }

    if minimum_tier not in tier_hierarchy:
        raise ValueError(f"Invalid tier: {minimum_tier}. Must be one of {list(tier_hierarchy.keys())}")

    def decorator(f: Callable) -> Callable:
        @wraps(f)
        def decorated_function(*args, **kwargs):
            # Get current user (set by @require_auth)
            user = get_current_user()

            if not user:
                logger.error("require_tier used without require_auth", path=request.path)
                return unauthorized_response(message="Authentication required.")

            # Get user's tier level
            user_tier = user.tier
            user_tier_level = tier_hierarchy.get(user_tier, 0)
            required_tier_level = tier_hierarchy[minimum_tier]

            # Check if user has sufficient tier
            if user_tier_level < required_tier_level:
                logger.warning(
                    "Access denied - insufficient tier",
                    user_id=user.id,
                    user_tier=user_tier,
                    required_tier=minimum_tier,
                    path=request.path
                )
                return forbidden_response(
                    message=f"This feature requires {minimum_tier.capitalize()} tier or higher. "
                            f"Your current tier: {user_tier.capitalize()}."
                )

            logger.debug(
                "Tier requirement met",
                user_id=user.id,
                user_tier=user_tier,
                required_tier=minimum_tier,
                path=request.path
            )

            # Call the original function
            return f(*args, **kwargs)

        return decorated_function

    return decorator


def require_email_verified(f: Callable) -> Callable:
    """
    Decorator to require email verification for a route.

    This decorator must be used AFTER @require_auth.

    Args:
        f: The Flask route function to wrap

    Returns:
        Wrapped function with email verification check

    Usage:
        @app.route('/verified-only')
        @require_auth
        @require_email_verified
        def verified_only():
            return "You can only see this if your email is verified"
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # Get current user (set by @require_auth)
        user = get_current_user()

        if not user:
            logger.error("require_email_verified used without require_auth", path=request.path)
            return unauthorized_response(message="Authentication required.")

        # Check if email is verified
        if not user.email_verified:
            logger.warning(
                "Access denied - email not verified",
                user_id=user.id,
                email=user.email,
                path=request.path
            )
            return forbidden_response(
                message="Email verification required. Please check your inbox and verify your email address."
            )

        logger.debug("Email verification confirmed", user_id=user.id, email=user.email, path=request.path)

        # Call the original function
        return f(*args, **kwargs)

    return decorated_function


def optional_auth(f: Callable) -> Callable:
    """
    Decorator for routes that optionally use authentication.

    This decorator will attach the user to g.current_user if authenticated,
    but will NOT block the request if not authenticated. Use this for routes
    that should behave differently based on authentication status.

    Args:
        f: The Flask route function to wrap

    Returns:
        Wrapped function with optional authentication

    Usage:
        @app.route('/landing')
        @optional_auth
        def landing():
            user = get_current_user()
            if user:
                return f"Welcome back, {user.name}!"
            else:
                return "Welcome! Please log in."
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # Extract session token from cookie
        token = extract_session_token()

        if token:
            # Verify session and get user
            user = verify_session(token)

            if user:
                # Attach user to request context
                g.current_user = user
                logger.debug("Optional auth - user authenticated", user_id=user.id, path=request.path)
            else:
                logger.debug("Optional auth - invalid session", path=request.path)
        else:
            logger.debug("Optional auth - no session token", path=request.path)

        # Call the original function regardless of authentication
        return f(*args, **kwargs)

    return decorated_function


def get_user_tier() -> str:
    """
    Get the current user's tier.

    Returns:
        Tier string (free, basic, premium, elite), defaults to 'free' if not authenticated

    Usage:
        @app.route('/dashboard')
        @require_auth
        def dashboard():
            tier = get_user_tier()
            return f"Your tier: {tier}"
    """
    user = get_current_user()
    if user:
        return user.tier
    return 'free'


def is_tier_sufficient(required_tier: str) -> bool:
    """
    Check if the current user's tier meets the requirement.

    Args:
        required_tier: Required tier level

    Returns:
        True if user's tier is sufficient, False otherwise

    Usage:
        @app.route('/feature')
        @require_auth
        def feature():
            if is_tier_sufficient('premium'):
                return "Premium features enabled"
            else:
                return "Upgrade to premium for more features"
    """
    tier_hierarchy = {
        'free': 0,
        'basic': 1,
        'premium': 2,
        'elite': 3
    }

    user = get_current_user()
    if not user:
        return False

    user_tier_level = tier_hierarchy.get(user.tier, 0)
    required_tier_level = tier_hierarchy.get(required_tier, 0)

    return user_tier_level >= required_tier_level
