from flask import flash, jsonify, redirect, render_template, request, session, url_for
from flask_login import current_user
from flask_wtf.csrf import CSRFError
from werkzeug.exceptions import HTTPException

from app.utils.security import is_safe_url, safe_redirect_target


def _wants_json_response() -> bool:
    if request.path.startswith("/api/"):
        return True
    if request.headers.get("X-Requested-With") == "XMLHttpRequest":
        return True
    best = request.accept_mimetypes.best
    return best == "application/json" and request.accept_mimetypes[best] >= request.accept_mimetypes["text/html"]


def _fallback_target() -> str:
    referrer = request.referrer
    if referrer and is_safe_url(referrer) and referrer != request.url:
        return referrer
    if current_user.is_authenticated:
        return url_for("main.dashboard")
    return url_for("auth.login")


def _notification_error_response(message: str, status_code: int, *, category: str = "danger"):
    if _wants_json_response():
        return jsonify({"error": "request_failed", "message": message}), status_code
    flash(message, category)
    return redirect(_fallback_target())


def _is_asset_like_request() -> bool:
    last_segment = request.path.rsplit("/", 1)[-1]
    return "." in last_segment and not request.accept_mimetypes.accept_html


def register_error_handlers(app) -> None:
    @app.errorhandler(CSRFError)
    def csrf_error(error):
        app.logger.warning("CSRF validation failed for %s %s: %s", request.method, request.path, error.description)
        session.pop("csrf_token", None)

        if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html:
            return jsonify({"error": "csrf_error", "message": "Your form session expired. Refresh the page and try again."}), 400

        flash("Your form session expired or became invalid. Please try again.", "warning")
        if request.endpoint == "auth.set_locale":
            target = safe_redirect_target(url_for("auth.login"))
            return redirect(target)

        if request.method != "GET":
            if current_user.is_authenticated:
                target = request.path or url_for("main.dashboard")
            else:
                target = request.path or url_for("auth.login")
            return redirect(target)

        return render_template("errors/400.html"), 400

    @app.errorhandler(403)
    def forbidden(error):
        return _notification_error_response("You do not have permission to complete this action.", 403, category="warning")

    @app.errorhandler(400)
    def bad_request(error):
        return _notification_error_response("The request data is invalid or incomplete.", 400, category="warning")

    @app.errorhandler(404)
    def not_found(error):
        if request.path.startswith("/static/") or _is_asset_like_request():
            return render_template("errors/404.html"), 404
        return _notification_error_response("The requested page or resource was not found.", 404, category="warning")

    @app.errorhandler(405)
    def method_not_allowed(error):
        return _notification_error_response("This action is not allowed for the current request.", 405, category="warning")

    @app.errorhandler(413)
    def too_large(error):
        return _notification_error_response("The uploaded file is larger than the allowed size.", 413)

    @app.errorhandler(429)
    def too_many_requests(error):
        return _notification_error_response("Too many requests. Please wait a moment and try again.", 429, category="warning")

    @app.errorhandler(500)
    def server_error(error):
        app.logger.exception("Unhandled server error: %s", error)
        return _notification_error_response("An unexpected error occurred. Please try again.", 500)

    @app.errorhandler(Exception)
    def unhandled_exception(error):
        if isinstance(error, HTTPException):
            return error
        app.logger.exception("Unhandled exception during %s %s: %s", request.method, request.path, error)
        return _notification_error_response("An unexpected error occurred. Please try again.", 500)
