"""Compatibility detector API (legacy).

Historically, this project documented a ``pyimgano.detectors`` module with
classes such as ``IsolationForestDetector`` and ``AutoencoderDetector``.

The recommended, stable API is now the registry-driven factory in
``pyimgano.models``:

    - ``pyimgano.models.list_models()``
    - ``pyimgano.models.create_model(name, **kwargs)``

This module exists to keep older code working with minimal changes.
"""

from __future__ import annotations

import importlib.util
from dataclasses import dataclass
from typing import Any, Optional, Sequence

import numpy as np

from pyimgano.features.identity import IdentityExtractor as IdentityFeatureExtractor
from pyimgano.models.cof import VisionCOF as _VisionCOF
from pyimgano.models.copod import VisionCOPOD as _VisionCOPOD
from pyimgano.models.ecod import VisionECOD as _VisionECOD
from pyimgano.models.feature_bagging import VisionFeatureBagging as _VisionFeatureBagging
from pyimgano.models.gmm import VisionGMM as _VisionGMM
from pyimgano.models.hbos import VisionHBOS as _VisionHBOS
from pyimgano.models.iforest import VisionIForest as _VisionIForest
from pyimgano.models.kde import VisionKDE as _VisionKDE
from pyimgano.models.knn import VisionKNN as _VisionKNN
from pyimgano.models.kpca import VisionKPCA as _VisionKPCA
from pyimgano.models.loci import VisionLOCI as _VisionLOCI
from pyimgano.models.mad import VisionMAD as _VisionMAD
from pyimgano.models.ocsvm import VisionOCSVM as _VisionOCSVM
from pyimgano.models.pca import VisionPCA as _VisionPCA


def _ensure_extractor(feature_extractor):
    return feature_extractor if feature_extractor is not None else IdentityFeatureExtractor()


# ---------------------------------------------------------------------------
# Classical / shallow wrappers (native)
# ---------------------------------------------------------------------------


class HistogramBasedDetector(_VisionHBOS):
    """Legacy name mapped to HBOS (histogram-based outlier score)."""

    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class MADDetector(_VisionMAD):
    def __init__(
        self,
        *,
        feature_extractor=None,
        contamination: float = 0.1,
        aggregation: str = "max",
        eps: float = 1e-12,
        consistency_correction: bool = True,
    ) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            aggregation=aggregation,
            eps=eps,
            consistency_correction=consistency_correction,
        )


class KNNDetector(_VisionKNN):
    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class COFDetector(_VisionCOF):
    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class LOCIDetector(_VisionLOCI):
    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class ECODDetector(_VisionECOD):
    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class COPODDetector(_VisionCOPOD):
    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class GMMDetector(_VisionGMM):
    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class KDEDetector(_VisionKDE):
    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class IsolationForestDetector(_VisionIForest):
    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class PCADetector(_VisionPCA):
    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class KernelPCADetector(_VisionKPCA):
    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class OCSVMDetector(_VisionOCSVM):
    def __init__(self, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


class FeatureBaggingDetector(_VisionFeatureBagging):
    def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
        super().__init__(
            feature_extractor=_ensure_extractor(feature_extractor),
            contamination=contamination,
            **kwargs,
        )


# ---------------------------------------------------------------------------
# Deep (feature-based) wrappers
# ---------------------------------------------------------------------------


def _normalize_legacy_autoencoder_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]:
    normalized = dict(kwargs)
    # Common legacy aliases
    if "epoch_num" in normalized and "epochs" not in normalized:
        normalized["epochs"] = normalized.pop("epoch_num")
    if "learning_rate" in normalized and "lr" not in normalized:
        normalized["lr"] = normalized.pop("learning_rate")

    # Legacy hidden_neuron_list -> hidden_neurons
    if "hidden_neuron_list" in normalized and "hidden_neurons" not in normalized:
        normalized["hidden_neurons"] = normalized.pop("hidden_neuron_list")

    # Legacy "hidden_dims" + "encoding_dim" -> symmetric hidden_neurons
    if "hidden_neurons" not in normalized:
        hidden_dims = normalized.pop("hidden_dims", None)
        encoding_dim = normalized.pop("encoding_dim", None)
        if hidden_dims is not None and encoding_dim is not None:
            hidden_dims_list = list(hidden_dims)
            normalized["hidden_neurons"] = (
                hidden_dims_list + [int(encoding_dim)] + list(reversed(hidden_dims_list))
            )
    # Legacy input_dim is inferred from features; keep as noop for compatibility.
    normalized.pop("input_dim", None)

    # The legacy "AutoencoderDetector" implies a reconstruction-flavored model.
    # We map it to DeepSVDD in autoencoder mode for a close behavior match.
    normalized.setdefault("use_autoencoder", True)
    return normalized


def _torch_available() -> bool:
    return importlib.util.find_spec("torch") is not None


def _make_missing_torch_autoencoder_detector():
    class AutoencoderDetector:  # noqa: D401 - legacy compatibility shim
        """Legacy AutoencoderDetector (requires torch).

        Install it via:
          pip install 'pyimgano[torch]'
        """

        def __init__(self, *args: Any, **kwargs: Any) -> None:  # noqa: ANN401 - legacy shim
            del args, kwargs
            from pyimgano.utils.optional_deps import require

            require("torch", extra="torch", purpose="pyimgano.detectors.AutoencoderDetector")
            raise RuntimeError(
                "Unreachable: torch was required but AutoencoderDetector was not materialized."
            )

    return AutoencoderDetector


def _make_autoencoder_detector_class():
    from pyimgano.models.deep_svdd import VisionDeepSVDD as _VisionDeepSVDD

    class AutoencoderDetector(_VisionDeepSVDD):
        def __init__(self, *, feature_extractor=None, contamination: float = 0.1, **kwargs) -> None:
            super().__init__(
                feature_extractor=_ensure_extractor(feature_extractor),
                contamination=contamination,
                **_normalize_legacy_autoencoder_kwargs(kwargs),
            )

    return AutoencoderDetector


def __getattr__(name: str):  # pragma: no cover - exercised via import patterns
    if name != "AutoencoderDetector":
        raise AttributeError(name)

    if not _torch_available():
        value = _make_missing_torch_autoencoder_detector()
        globals()[name] = value
        return value

    value = _make_autoencoder_detector_class()
    globals()[name] = value
    return value


def __dir__() -> list[str]:  # pragma: no cover - tooling convenience
    return sorted(set(globals()) | {"AutoencoderDetector"})


__all__ = [
    "IdentityFeatureExtractor",
    # Classic/statistical
    "HistogramBasedDetector",
    "MADDetector",
    "KNNDetector",
    "COFDetector",
    "LOCIDetector",
    "ECODDetector",
    "COPODDetector",
    "GMMDetector",
    "KDEDetector",
    "IsolationForestDetector",
    "PCADetector",
    "KernelPCADetector",
    "OCSVMDetector",
    "FeatureBaggingDetector",
    # Deep (feature-based)
    "AutoencoderDetector",
]
