"""
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

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


@auth_bp.route("/login", methods=["GET", "POST"])
def login():
    """
    Login page and handler.
    Supports browser password managers with standard autocomplete attributes.
    """
    if request.method == "GET":
        # 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
        )

    # 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
        ), 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
        ), 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}"
            )

    # 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")

    # 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", "POST"])
def register():
    """
    Registration page and handler.
    Creates new encrypted database for user with clear warnings about password recovery.
    """
    if request.method == "GET":
        return render_template(
            "auth/register.html", has_encryption=db_manager.has_encryption
        )

    # 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
    if username and db_manager.user_exists(username):
        errors.append("Username already exists")

    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}")

        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(),
        }
    )
