from __future__ import annotations

import inspect
from typing import Any

from .base import PipelineComponent


def _extract_default_params(sklearn_class: type) -> dict[str, Any]:
    try:
        signature = inspect.signature(sklearn_class.__init__)
    except Exception:
        return {}

    defaults: dict[str, Any] = {}
    for name, parameter in signature.parameters.items():
        if name == "self":
            continue
        default = parameter.default
        defaults[name] = None if default is inspect._empty else default
    return defaults


def make_adapter(sklearn_class: type) -> type[PipelineComponent]:
    """Create a PipelineComponent adapter class for a sklearn estimator class."""

    default_params = _extract_default_params(sklearn_class)

    def _init(self, **params: Any) -> None:
        merged = dict(default_params)
        merged.update(params or {})
        PipelineComponent.__init__(self, **merged)

    def _fit(self, X: Any, y: Any = None) -> PipelineComponent:
        model = sklearn_class(**self.params)
        if y is None:
            try:
                model.fit(X)
            except TypeError:
                model.fit(X, y)
        else:
            model.fit(X, y)
        self._fitted_model = model
        return self

    def _transform(self, X: Any) -> Any:
        if self._fitted_model is None:
            raise ValueError("Component is not fitted. Run fit before transform.")
        if hasattr(self._fitted_model, "transform"):
            return self._fitted_model.transform(X)
        return X

    methods: dict[str, Any] = {
        "__init__": _init,
        "fit": _fit,
        "transform": _transform,
        "fit_transform": PipelineComponent.fit_transform,
        "get_params": PipelineComponent.get_params,
        "set_params": PipelineComponent.set_params,
        "is_fitted": PipelineComponent.is_fitted,
        "to_dict": PipelineComponent.to_dict,
    }

    if hasattr(sklearn_class, "predict"):

        def _predict(self, X: Any) -> Any:
            if self._fitted_model is None:
                raise ValueError("Component is not fitted. Run fit before predict.")
            return self._fitted_model.predict(X)

        methods["predict"] = _predict

    if hasattr(sklearn_class, "predict_proba"):

        def _predict_proba(self, X: Any) -> Any:
            if self._fitted_model is None:
                raise ValueError("Component is not fitted. Run fit before predict_proba.")
            return self._fitted_model.predict_proba(X)

        methods["predict_proba"] = _predict_proba

    adapter_class = type(
        f"{sklearn_class.__name__}Adapter",
        (PipelineComponent,),
        methods,
    )
    adapter_class._sklearn_class = sklearn_class  # type: ignore[attr-defined]
    adapter_class._default_params = default_params  # type: ignore[attr-defined]
    adapter_class.__doc__ = f"Auto-generated adapter for sklearn estimator {sklearn_class.__name__}."
    return adapter_class

