import inspect
import json
from typing import Any, Callable, Dict, List, Awaitable

from quart import Blueprint, jsonify, request
from quart.views import MethodView

from taranis_base_bot.log import logger


class InferenceView(MethodView):
    """
    Generic POST endpoint that:
    - parses request JSON via `request_parser`
    - calls `predict_fn(**kwargs)`
    - returns JSON
    """

    def __init__(
        self,
        predict_fn: Callable[..., Awaitable[Any]],
        request_parser: Callable[[Any], Dict[str, Any]],
    ) -> None:
        super().__init__()
        self._predict_fn = predict_fn
        self._parse = request_parser

    async def post(self):
        data = await request.get_json()
        sanitized_payload = json.dumps(data).replace("\r", "").replace("\n", "")
        logger.debug(f"Payload: {sanitized_payload}")

        if not isinstance(data, dict):
            return jsonify({"error": "Payload must be a dict!"}), 400
        try:
            kwargs = self._parse(data)
        except Exception as e:
            logger.error(f"Parsing payload failed with error: {e}")
            return jsonify({"error": "Could not parse payload. Check bot logs for more details."}), 400

        try:
            result = await self._predict_fn(**kwargs)
            logger.debug(f"Bot output: {result}")
            return jsonify(result)
        except Exception as e:
            logger.error(f"Bot failed with error: {e}")
            return jsonify({"error": "Bot execution failed. Check bot logs for more details."}), 400


class HealthView(MethodView):
    async def get(self):
        return jsonify({"status": "ok"})


class ModelInfoView(MethodView):
    def __init__(self, modelinfo_fn: Callable[[], Any]) -> None:
        super().__init__()
        self._modelinfo = modelinfo_fn

    async def get(self):
        result = self._modelinfo()
        if inspect.isawaitable(result):
            result = await result
        return jsonify(result)


def create_service_blueprint(
    *,
    name: str,
    url_prefix: str = "/",
    predict_fn: Callable[..., Awaitable[Any]] | None = None,
    modelinfo_fn: Callable[[], Any] | Callable[[], Awaitable[Any]],
    request_parser: Callable[[Any], Dict[str, Any]],
    method_decorators: List[Callable] | None = None,
):
    """
    Returns a Blueprint with three routes:
      POST   "/"         -> InferenceView
      GET    "/health"   -> HealthView
      GET    "/modelinfo"-> ModelInfoView

    - `method_decorators` are applied to the POST method
    """
    bp = Blueprint(name, __name__, url_prefix=url_prefix)

    inference_view = InferenceView.as_view(
        f"{name}_predict",
        predict_fn=predict_fn,
        request_parser=request_parser,
    )

    if method_decorators:
        for dec in reversed(method_decorators):
            inference_view = dec(inference_view)

    health_view = HealthView.as_view(f"{name}_health")
    modelinfo_view = ModelInfoView.as_view(
        f"{name}_modelinfo",
        modelinfo_fn=modelinfo_fn,
    )

    bp.add_url_rule("/", view_func=inference_view, methods=["POST"])
    bp.add_url_rule("/health", view_func=health_view, methods=["GET"])
    bp.add_url_rule("/modelinfo", view_func=modelinfo_view, methods=["GET"])

    return bp
