"""
Authentication routes for login, register, and logout.
Uses SQLCipher encrypted databases with browser password manager support.
"""

from datetime import datetime, timezone, UTC
from pathlib import PurePosixPath

from flask import (
    Blueprint,
    flash,
    jsonify,
    redirect,
    render_template,
    request,
    session,
    url_for,
)
from loguru import logger

from ...database.auth_db import get_auth_db_session
from ...database.encrypted_db import db_manager
from ...database.models.auth import User
from .session_manager import SessionManager
from ..server_config import load_server_config
from ..utils.rate_limiter import login_limit, registration_limit

auth_bp = Blueprint("auth", __name__, url_prefix="/auth")
session_manager = SessionManager()


@auth_bp.route("/csrf-token", methods=["GET"])
def get_csrf_token():
    """
    Get CSRF token for API requests.
    Returns the current CSRF token for the session.
    This endpoint makes it easy for API clients to get the CSRF token
    programmatically without parsing HTML.
    """
    from flask_wtf.csrf import generate_csrf

    # Generate or get existing CSRF token for this session
    token = generate_csrf()

    return jsonify({"csrf_token": token}), 200


@auth_bp.route("/login", methods=["GET"])
def login_page():
    """
    Login page (GET only).
    Not rate limited - viewing the page should always work.
    """
    config = load_server_config()
    # Check if already logged in
    if session.get("username"):
        return redirect(url_for("index"))

    return render_template(
        "auth/login.html",
        has_encryption=db_manager.has_encryption,
        allow_registrations=config.get("allow_registrations", True),
    )


@auth_bp.route("/login", methods=["POST"])
@login_limit
def login():
    """
    Login handler (POST only).
    Rate limited to 5 attempts per 15 minutes per IP to prevent brute force attacks.
    """
    config = load_server_config()
    # POST - Handle login
    username = request.form.get("username", "").strip()
    password = request.form.get("password", "")
    remember = request.form.get("remember", "false") == "true"

    if not username or not password:
        flash("Username and password are required", "error")
        return render_template(
            "auth/login.html",
            has_encryption=db_manager.has_encryption,
            allow_registrations=config.get("allow_registrations", True),
        ), 400

    # Try to open user's encrypted database
    engine = db_manager.open_user_database(username, password)

    if engine is None:
        # Invalid credentials or database doesn't exist
        logger.warning(f"Failed login attempt for username: {username}")
        flash("Invalid username or password", "error")
        return render_template(
            "auth/login.html",
            has_encryption=db_manager.has_encryption,
            allow_registrations=config.get("allow_registrations", True),
        ), 401

    # Check if user has settings loaded (first login after migration)
    from ..services.settings_manager import SettingsManager

    db_session = db_manager.get_session(username)
    if db_session:
        settings_manager = SettingsManager(db_session)

        # Check if DB version matches package version
        if not settings_manager.db_version_matches_package():
            logger.info(
                f"Database version mismatch for {username} - loading missing default settings"
            )
            # Load defaults but preserve existing user settings
            settings_manager.load_from_defaults_file(
                commit=True, overwrite=False
            )
            settings_manager.update_db_version()
            logger.info(
                f"Missing default settings loaded and version updated for user {username}"
            )

    # Initialize library system (source types and default collection)
    from ...database.library_init import initialize_library_for_user

    try:
        init_results = initialize_library_for_user(username, password)
        if init_results.get("success"):
            logger.info(f"Library system initialized for user {username}")
        else:
            logger.warning(
                f"Library initialization issue for {username}: {init_results.get('error', 'Unknown error')}"
            )
    except Exception:
        logger.exception(f"Error initializing library for {username}")
        # Don't block login on library init failure

    # Success! Create session
    session_id = session_manager.create_session(username, remember)
    session["session_id"] = session_id
    session["username"] = username
    session.permanent = remember

    # Store password temporarily for post-login database access
    from ...database.temp_auth import temp_auth_store

    auth_token = temp_auth_store.store_auth(username, password)
    session["temp_auth_token"] = auth_token

    # Also store in session password store for metrics access
    from ...database.session_passwords import session_password_store

    session_password_store.store_session_password(
        username, session_id, password
    )

    # Update last login in auth database
    auth_db = get_auth_db_session()
    user = auth_db.query(User).filter_by(username=username).first()
    if user:
        user.last_login = datetime.now(UTC)

    # Notify the news scheduler about the user login
    try:
        from ...news.subscription_manager.scheduler import get_news_scheduler

        scheduler = get_news_scheduler()
        if scheduler.is_running:
            scheduler.update_user_info(username, password)
            logger.info(f"Updated scheduler with user info for {username}")
    except Exception:
        logger.exception("Could not update scheduler on login")

    auth_db.commit()
    auth_db.close()

    logger.info(f"User {username} logged in successfully")

    # Clear the model cache on login to ensure fresh provider data
    try:
        from ...database.models import ProviderModel

        user_db_session = db_manager.get_session(username)
        if user_db_session:
            # Delete all cached models for this user
            deleted_count = user_db_session.query(ProviderModel).delete()
            user_db_session.commit()
            logger.info(
                f"Cleared {deleted_count} cached models for user {username} on login"
            )
    except Exception:
        logger.exception("Failed to clear model cache on login")

    # Redirect to original requested page or dashboard
    # Validate redirect URL to prevent open redirect vulnerability
    from urllib.parse import urlparse

    next_page = request.args.get("next", url_for("index"))

    # Parse the URL and validate its structure
    parsed_url = urlparse(next_page)

    # Whitelist of allowed paths - add more as needed
    allowed_paths = {
        "/",
        "/dashboard",
        "/profile",
        "/settings",
        "/research",
        "/history",
        "/metrics",
        "/benchmark",
    }

    # Also allow any path generated by url_for (they start with /)
    safe_redirect = url_for("index")  # Default safe URL

    # Validate the URL: must be relative (no netloc) and safe
    if next_page and not parsed_url.netloc and not parsed_url.scheme:
        # Normalize the path to prevent tricks like //example.com
        # Using PurePosixPath for URL path normalization (not filesystem paths)
        normalized_path = (
            str(PurePosixPath(parsed_url.path)) if parsed_url.path else ""
        )

        # Check if it's in the whitelist or is a safe relative path
        if parsed_url.path in allowed_paths:
            safe_redirect = parsed_url.path  # Use parsed path, not raw input
        elif (
            parsed_url.path
            and parsed_url.path.startswith("/")
            and not parsed_url.path.startswith("//")
        ):
            # Additional checks for safety
            if ".." not in parsed_url.path and normalized_path.startswith("/"):
                safe_redirect = (
                    parsed_url.path
                )  # Use parsed path, not raw input
            else:
                logger.warning(
                    f"Blocked potentially unsafe redirect with path traversal: {next_page}"
                )
        else:
            logger.warning(
                f"Blocked potentially unsafe redirect to: {next_page}"
            )
    elif next_page:
        logger.warning(f"Blocked redirect with protocol/domain: {next_page}")

    return redirect(safe_redirect)


@auth_bp.route("/register", methods=["GET"])
def register_page():
    """
    Registration page (GET only).
    Not rate limited - viewing the page should always work.
    """
    config = load_server_config()
    if not config.get("allow_registrations", True):
        flash("New user registrations are currently disabled.", "error")
        return redirect(url_for("auth.login_page"))

    return render_template(
        "auth/register.html", has_encryption=db_manager.has_encryption
    )


@auth_bp.route("/register", methods=["POST"])
@registration_limit
def register():
    """
    Registration handler (POST only).
    Creates new encrypted database for user with clear warnings about password recovery.
    Rate limited to 3 attempts per hour per IP to prevent registration spam.
    """
    config = load_server_config()
    if not config.get("allow_registrations", True):
        flash("New user registrations are currently disabled.", "error")
        return redirect(url_for("auth.login_page"))

    # POST - Handle registration
    username = request.form.get("username", "").strip()
    password = request.form.get("password", "")
    confirm_password = request.form.get("confirm_password", "")
    acknowledge = request.form.get("acknowledge", "false") == "true"

    # Validation
    errors = []

    if not username:
        errors.append("Username is required")
    elif len(username) < 3:
        errors.append("Username must be at least 3 characters")
    elif not username.replace("_", "").replace("-", "").isalnum():
        errors.append(
            "Username can only contain letters, numbers, underscores, and hyphens"
        )

    if not password:
        errors.append("Password is required")
    elif len(password) < 8:
        errors.append("Password must be at least 8 characters")

    if password != confirm_password:
        errors.append("Passwords do not match")

    if not acknowledge:
        errors.append(
            "You must acknowledge that password recovery is not possible"
        )

    # Check if user already exists
    # Use generic error message to prevent account enumeration
    if username and db_manager.user_exists(username):
        errors.append("Registration failed. Please try a different username.")

    if errors:
        for error in errors:
            flash(error, "error")
        return render_template(
            "auth/register.html", has_encryption=db_manager.has_encryption
        ), 400

    try:
        # Create user in auth database
        auth_db = get_auth_db_session()
        new_user = User(username=username)
        auth_db.add(new_user)
        auth_db.commit()
        auth_db.close()

        # Create encrypted database for user
        db_manager.create_user_database(username, password)

        # Auto-login after registration
        session_id = session_manager.create_session(username, False)
        session["session_id"] = session_id
        session["username"] = username

        # Store password temporarily for post-registration database access
        from ...database.temp_auth import temp_auth_store

        auth_token = temp_auth_store.store_auth(username, password)
        session["temp_auth_token"] = auth_token

        # Also store in session password store for metrics access
        from ...database.session_passwords import session_password_store

        session_password_store.store_session_password(
            username, session_id, password
        )

        # Notify the news scheduler about the new user
        try:
            from ...news.subscription_manager.scheduler import (
                get_news_scheduler,
            )

            scheduler = get_news_scheduler()
            if scheduler.is_running:
                scheduler.update_user_info(username, password)
                logger.info(
                    f"Updated scheduler with new user info for {username}"
                )
        except Exception:
            logger.exception("Could not update scheduler on registration")

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

        # Initialize library system (source types and default collection)
        from ...database.library_init import initialize_library_for_user

        try:
            init_results = initialize_library_for_user(username, password)
            if init_results.get("success"):
                logger.info(
                    f"Library system initialized for new user {username}"
                )
            else:
                logger.warning(
                    f"Library initialization issue for {username}: {init_results.get('error', 'Unknown error')}"
                )
        except Exception:
            logger.exception(
                f"Error initializing library for new user {username}"
            )
            # Don't block registration on library init failure

        return redirect(url_for("index"))

    except Exception:
        logger.exception(f"Registration failed for {username}")

        # Rollback user creation if database creation failed
        auth_db.rollback()
        auth_db.close()

        flash("Registration failed. Please try again.", "error")
        return render_template(
            "auth/register.html", has_encryption=db_manager.has_encryption
        ), 500


@auth_bp.route("/logout", methods=["GET", "POST"])
def logout():
    """
    Logout handler.
    Clears session and closes database connections.
    Supports both GET (for direct navigation) and POST (for form submission).
    """
    username = session.get("username")
    session_id = session.get("session_id")

    if username:
        # Close database connection
        db_manager.close_user_database(username)

        # Clear session
        if session_id:
            session_manager.destroy_session(session_id)

            # Clear session password
            from ...database.session_passwords import session_password_store

            session_password_store.clear_session(username, session_id)

        session.clear()

        logger.info(f"User {username} logged out")
        flash("You have been logged out successfully", "info")

    return redirect(url_for("auth.login"))


@auth_bp.route("/check", methods=["GET"])
def check_auth():
    """
    Check if user is authenticated (for AJAX requests).
    """
    if session.get("username"):
        return jsonify({"authenticated": True, "username": session["username"]})
    else:
        return jsonify({"authenticated": False}), 401


@auth_bp.route("/change-password", methods=["GET", "POST"])
def change_password():
    """
    Change password for current user.
    Requires current password and re-encrypts database.
    """
    username = session.get("username")
    if not username:
        return redirect(url_for("auth.login"))

    if request.method == "GET":
        return render_template("auth/change_password.html")

    # POST - Handle password change
    current_password = request.form.get("current_password", "")
    new_password = request.form.get("new_password", "")
    confirm_password = request.form.get("confirm_password", "")

    # Validation
    errors = []

    if not current_password:
        errors.append("Current password is required")

    if not new_password:
        errors.append("New password is required")
    elif len(new_password) < 8:
        errors.append("New password must be at least 8 characters")

    if new_password != confirm_password:
        errors.append("New passwords do not match")

    if current_password == new_password:
        errors.append("New password must be different from current password")

    if errors:
        for error in errors:
            flash(error, "error")
        return render_template("auth/change_password.html"), 400

    # Attempt password change
    success = db_manager.change_password(
        username, current_password, new_password
    )

    if success:
        # Clear session to force re-login with new password
        session.clear()

        logger.info(f"Password changed for user {username}")
        flash(
            "Password changed successfully. Please login with your new password.",
            "success",
        )
        return redirect(url_for("auth.login"))
    else:
        flash("Current password is incorrect", "error")
        return render_template("auth/change_password.html"), 401


@auth_bp.route("/integrity-check", methods=["GET"])
def integrity_check():
    """
    Check database integrity for current user.
    """
    username = session.get("username")
    if not username:
        return jsonify({"error": "Not authenticated"}), 401

    is_valid = db_manager.check_database_integrity(username)

    return jsonify(
        {
            "username": username,
            "integrity": "valid" if is_valid else "corrupted",
            "timestamp": datetime.now(timezone.utc).isoformat(),
        }
    )
