from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Callable, Dict, Literal, Optional

from mockmesh.core.errors import InterceptError
from mockmesh.core.logger import get_logger

_log = get_logger("fallback")


@dataclass
class FallbackConfig:

    mode: Literal["mock", "passthrough", "error"] = "mock"
    provider_overrides: Dict[str, str] = field(default_factory=dict)
    on_intercept_error: Optional[Callable[..., Any]] = field(default=None, repr=False)

    def effective_mode(self, provider: str) -> str:
        return self.provider_overrides.get(provider, self.mode)


def safe_intercept(
    *,
    provider: str,
    operation: str,
    handler_fn: Callable[..., Any],
    handler_args: tuple = (),
    handler_kwargs: dict | None = None,
    fallback_config: FallbackConfig | None,
    original_fn: Callable[..., Any] | None = None,
    original_args: tuple = (),
    original_kwargs: dict | None = None,
    generic_response: Any = None,
    custom_handlers: dict | None = None,
) -> Any:
    handler_kwargs = handler_kwargs or {}
    original_kwargs = original_kwargs or {}
    fallback_config = fallback_config or FallbackConfig()

    if custom_handlers:
        custom_fn = custom_handlers.get((provider, operation))
        if custom_fn is not None:
            try:
                result = custom_fn(*handler_args, **handler_kwargs)
                _log.info(
                    "Custom handler executed",
                    extra={"mm_provider": provider, "mm_operation": operation},
                )
                return result
            except Exception as exc:
                _log.warning(
                    "Custom handler failed, falling through to default handler",
                    extra={
                        "mm_provider": provider,
                        "mm_operation": operation,
                        "mm_error": str(exc),
                    },
                )

    try:
        return handler_fn(*handler_args, **handler_kwargs)
    except Exception as exc:
        _log.warning(
            "Interceptor error",
            extra={
                "mm_provider": provider,
                "mm_operation": operation,
                "mm_error": str(exc),
                "mm_fallback_mode": fallback_config.effective_mode(provider),
            },
        )

        if fallback_config.on_intercept_error is not None:
            try:
                result = fallback_config.on_intercept_error(provider, operation, exc)
                if result is not None:
                    _log.info(
                        "on_intercept_error callback returned a response",
                        extra={"mm_provider": provider, "mm_operation": operation},
                    )
                    return result
            except Exception as cb_exc:
                _log.warning(
                    "on_intercept_error callback raised",
                    extra={"mm_error": str(cb_exc)},
                )

        mode = fallback_config.effective_mode(provider)

        if mode == "passthrough":
            if original_fn is not None:
                _log.info(
                    "Falling through to real service",
                    extra={"mm_provider": provider, "mm_operation": operation},
                )
                return original_fn(*original_args, **original_kwargs)
            _log.warning(
                "Passthrough requested but no original function available; "
                "returning generic mock",
                extra={"mm_provider": provider, "mm_operation": operation},
            )
            return generic_response

        if mode == "error":
            raise InterceptError(provider, operation, exc) from exc

        return generic_response
