"""
Authentication decorators for protecting routes.
"""

from functools import wraps
from typing import Optional

from flask import g, jsonify, redirect, request, session, url_for
from loguru import logger
from sqlalchemy.orm import Session

from ...database.encrypted_db import db_manager
from ...security.url_validator import URLValidator


def _safe_redirect_to_login():
    """
    Redirect to login with validated next parameter.

    Uses request.url as next parameter only if it passes
    security validation to prevent open redirect vulnerabilities.

    Returns:
        Flask redirect response
    """
    next_url = request.url
    # Validate that next URL is safe before using it
    if URLValidator.is_safe_redirect_url(next_url, request.host_url):
        return redirect(url_for("auth.login", next=next_url))
    # Fall back to login without next parameter if validation fails
    return redirect(url_for("auth.login"))


def _is_api_path(path: str) -> bool:
    """Detect API request paths that should receive JSON, not HTML redirects.

    Matches `/api/` anywhere in the path (so nested API blueprints like
    `/news/api/...` and `/library/api/...` work, not just top-level
    `/api/...`), and also paths that end in `/api` with no further
    segments (e.g. `/settings/api`, `/history/api` are JSON endpoints).

    The `api` segment must be slash-bounded — non-API paths like
    `/apidocs` or `/openapi.json` are not matched.
    """
    return "/api/" in path or path.endswith("/api")


def login_required(f):
    """
    Decorator to require authentication for a route.
    Redirects to login page if not authenticated.
    """

    @wraps(f)
    def decorated_function(*args, **kwargs):
        if "username" not in session:
            logger.debug(
                f"Unauthenticated access attempt to {request.endpoint}"
            )
            if _is_api_path(request.path):
                return jsonify({"error": "Authentication required"}), 401
            return _safe_redirect_to_login()

        # Check if we have an active database connection
        username = session["username"]
        if not db_manager.is_user_connected(username):
            # Use debug level to reduce log noise for persistent sessions
            logger.debug(
                f"No database connection for authenticated user {username}"
            )
            if _is_api_path(request.path):
                return jsonify({"error": "Database connection required"}), 401
            session.clear()
            return _safe_redirect_to_login()

        return f(*args, **kwargs)

    return decorated_function


def current_user():
    """
    Get the current authenticated user's username.
    Returns None if not authenticated.
    """
    return session.get("username")


def get_current_db_session() -> Optional[Session]:
    """
    Get the database session for the current user.
    Must be called within a login_required route.
    """
    username = current_user()
    if username:
        return db_manager.get_session(username)
    return None


def inject_current_user():
    """
    Flask before_request handler to inject current user into g.
    """
    g.current_user = current_user()
    if g.current_user:
        # Check connectivity
        if not db_manager.is_user_connected(g.current_user):
            # For API/auth routes, allow the request to continue
            if _is_api_path(request.path) or request.path.startswith("/auth/"):
                logger.debug(
                    f"No database for user {g.current_user} on API/auth route"
                )
            else:
                logger.debug(
                    f"Clearing stale session for user {g.current_user}"
                )
                session.clear()
                g.current_user = None
            g.db_session = None
        else:
            # Session will be created lazily by get_g_db_session() on first
            # access.  This avoids checking out a pool connection for requests
            # that never touch the database (status polls, health checks, etc.).
            g.db_session = None
    else:
        g.db_session = None
