"""
Flask routes for news API endpoints.
"""

import uuid
from functools import wraps

from flask import Blueprint, jsonify, request, session
from loguru import logger

from ...news import api as news_api
from ...news.exceptions import NewsAPIException
from ...utilities.url_utils import is_safe_custom_llm_endpoint
from ...security.decorators import require_json_body
from ..auth.decorators import login_required
from ...security.rate_limiter import limiter


def _reject_custom_endpoint(custom_endpoint):
    """Return a Flask 400 response if ``custom_endpoint`` fails SSRF
    validation, else None.

    Rejects cloud-metadata / link-local targets at the request boundary as
    fail-fast defense-in-depth (the OpenAI-compatible provider re-validates
    the same URL via assert_base_url_safe before the client is built).
    Private IPs and localhost are allowed because local LLMs live there;
    scheme-less endpoints are normalized exactly as the provider does.
    """
    if is_safe_custom_llm_endpoint(custom_endpoint):
        return None
    return (
        jsonify({"success": False, "error": "Invalid custom endpoint URL"}),
        400,
    )


def _is_valid_uuid(value: str) -> bool:
    """Return True if ``value`` parses as a UUID, False otherwise.

    Used to validate path/query subscription_id parameters before they
    reach the LIKE-pattern queries in ``news/api.py``. Without this
    check, a request like ``?subscription_id=%`` would expand the
    LIKE filter and match arbitrary subscriptions (enumeration vector,
    though not data exfiltration since user-DB isolation still applies).
    """
    try:
        uuid.UUID(str(value))
    except (ValueError, AttributeError, TypeError):
        return False
    return True


# Create blueprint
bp = Blueprint("news_api", __name__, url_prefix="/api/news")

# NOTE: Routes use session["username"] (not .get()) intentionally.
# @login_required guarantees the key exists; direct access fails fast
# if the decorator is ever removed.

# Shared rate limits for POST endpoints
_news_create_limit = limiter.shared_limit("10 per minute", scope="news_create")
_news_research_limit = limiter.shared_limit(
    "5 per minute", scope="news_research"
)
_news_feedback_limit = limiter.shared_limit(
    "30 per minute", scope="news_feedback"
)
_news_preferences_limit = limiter.shared_limit(
    "10 per minute", scope="news_preferences"
)


def handle_api_errors(f):
    """Decorator to handle API errors consistently across news endpoints."""

    @wraps(f)
    def wrapper(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except NewsAPIException:
            raise
        except Exception:
            logger.exception("Unexpected error in {}", f.__name__)
            return jsonify({"error": "Internal server error"}), 500

    return wrapper


@bp.errorhandler(NewsAPIException)
def handle_news_api_exception(error: NewsAPIException):
    """Handle NewsAPIException and convert to JSON response."""
    logger.error(
        "News API error: {} (status {})", error.error_code, error.status_code
    )
    return jsonify(error.to_dict()), error.status_code


@bp.route("/feed", methods=["GET"])
@login_required
@handle_api_errors
def get_news_feed():
    """Get personalized news feed."""
    user_id = session["username"]
    limit = request.args.get("limit", 20, type=int)
    limit = max(1, min(limit, 200))
    use_cache = request.args.get("use_cache", "true").lower() == "true"
    focus = request.args.get("focus")
    search_strategy = request.args.get("search_strategy")
    subscription_id = request.args.get("subscription_id")

    if subscription_id and not _is_valid_uuid(subscription_id):
        return jsonify(
            {
                "success": False,
                "error": "Invalid subscription_id",
            }
        ), 400

    result = news_api.get_news_feed(
        user_id=user_id,
        limit=limit,
        use_cache=use_cache,
        focus=focus,
        search_strategy=search_strategy,
        subscription_id=subscription_id,
    )

    return jsonify(result)


@bp.route("/subscriptions", methods=["GET"])
@login_required
@handle_api_errors
def get_subscriptions():
    """Get all subscriptions for the current user."""
    user_id = session["username"]
    result = news_api.get_subscriptions(user_id)
    return jsonify(result)


@bp.route("/subscriptions", methods=["POST"])
@login_required
@handle_api_errors
@_news_create_limit
@require_json_body()
def create_subscription():
    """Create a new subscription."""
    user_id = session["username"]
    data = request.get_json()

    bad_endpoint = _reject_custom_endpoint(data.get("custom_endpoint"))
    if bad_endpoint is not None:
        return bad_endpoint

    result = news_api.create_subscription(
        user_id=user_id,
        query=data.get("query"),
        subscription_type=data.get("type", "search"),
        refresh_minutes=data.get("refresh_minutes"),
        source_research_id=data.get("source_research_id"),
        model_provider=data.get("model_provider"),
        model=data.get("model"),
        search_strategy=data.get("search_strategy"),
        custom_endpoint=data.get("custom_endpoint"),
        name=data.get("name"),
        folder_id=data.get("folder_id"),
        is_active=data.get("is_active", True),
        search_engine=data.get("search_engine"),
        search_iterations=data.get("search_iterations"),
        questions_per_iteration=data.get("questions_per_iteration"),
    )

    return jsonify(result), 201


@bp.route("/subscriptions/<subscription_id>", methods=["GET"])
@login_required
@handle_api_errors
def get_subscription(subscription_id):
    """Get a single subscription by ID."""
    result = news_api.get_subscription(subscription_id)
    return jsonify(result)


@bp.route("/subscriptions/<subscription_id>", methods=["PUT", "PATCH"])
@login_required
@handle_api_errors
@require_json_body()
def update_subscription(subscription_id):
    """Update an existing subscription."""
    data = request.get_json()
    bad_endpoint = _reject_custom_endpoint(data.get("custom_endpoint"))
    if bad_endpoint is not None:
        return bad_endpoint
    result = news_api.update_subscription(subscription_id, data)
    return jsonify(result)


@bp.route("/subscriptions/<subscription_id>", methods=["DELETE"])
@login_required
@handle_api_errors
def delete_subscription(subscription_id):
    """Delete a subscription."""
    result = news_api.delete_subscription(subscription_id)
    return jsonify(result)


@bp.route("/subscriptions/<subscription_id>/history", methods=["GET"])
@login_required
@handle_api_errors
def get_subscription_history(subscription_id):
    """Get research history for a specific subscription."""
    if not _is_valid_uuid(subscription_id):
        return jsonify(
            {
                "success": False,
                "error": "Invalid subscription_id",
            }
        ), 400
    limit = request.args.get("limit", 20, type=int)
    limit = max(1, min(limit, 200))
    result = news_api.get_subscription_history(subscription_id, limit)
    return jsonify(result)


@bp.route("/feedback", methods=["POST"])
@login_required
@handle_api_errors
@_news_feedback_limit
@require_json_body()
def submit_feedback():
    """Submit feedback (vote) for a news card."""
    user_id = session["username"]
    data = request.get_json()
    card_id = data.get("card_id")
    vote = data.get("vote")

    if not card_id or vote not in ["up", "down"]:
        return jsonify({"error": "Invalid request"}), 400

    result = news_api.submit_feedback(card_id, user_id, vote)
    return jsonify(result)


@bp.route("/research", methods=["POST"])
@login_required
@handle_api_errors
@_news_research_limit
@require_json_body()
def research_news_item():
    """Perform deeper research on a news item."""
    data = request.get_json()
    card_id = data.get("card_id")
    depth = data.get("depth", "quick")

    if not card_id:
        return jsonify({"error": "card_id is required"}), 400

    result = news_api.research_news_item(card_id, depth)
    return jsonify(result)


@bp.route("/preferences", methods=["POST"])
@login_required
@handle_api_errors
@_news_preferences_limit
@require_json_body()
def save_preferences():
    """Save user preferences for news."""
    user_id = session["username"]
    preferences = request.get_json()
    result = news_api.save_news_preferences(user_id, preferences)
    return jsonify(result)


@bp.route("/categories", methods=["GET"])
@login_required
@handle_api_errors
def get_categories():
    """Get available news categories with counts."""
    result = news_api.get_news_categories()
    return jsonify(result)
