"""Versiones ligeras de algoritmos de scikit-learn usadas en las pruebas.

Estas implementaciones no pretenden ser equivalentes a las originales, sólo
aportan la interfaz necesaria para ejecutar los tests sin depender de
``scikit-learn`` real.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Iterable, Optional, Sequence

import numpy as np


@dataclass
class _BaseMemorizingClassifier:
    """Clasificador muy sencillo que memoriza los pares ``(X, y)``.

    La idea es ofrecer una API compatible con scikit-learn con el mínimo
    esfuerzo. Se mantiene la lista de ``classes_`` y se devuelven predicciones
    exactas para puntos ya vistos. Para puntos no vistos se elige la clase más
    frecuente.
    """

    classes_: Optional[np.ndarray] = None
    _memory: dict[tuple[float, ...], int] = field(default_factory=dict)

    def _standardize(self, X: Sequence[Sequence[float]]) -> np.ndarray:
        arr = np.asarray(X)
        if arr.ndim == 1:
            arr = arr.reshape(-1, 1)
        return arr

    def fit(self, X: Sequence[Sequence[float]], y: Sequence[int]) -> "_BaseMemorizingClassifier":
        data = self._standardize(X)
        targets = np.asarray(y)
        self.classes_ = np.unique(targets)
        for sample, target in zip(data, targets):
            self._memory[tuple(float(v) for v in sample)] = int(target)
        return self

    def partial_fit(
        self,
        X: Sequence[Sequence[float]],
        y: Sequence[int],
        classes: Optional[Iterable[int]] = None,
    ) -> "_BaseMemorizingClassifier":
        if classes is not None:
            classes_arr = np.asarray(list(classes))
            self.classes_ = classes_arr if self.classes_ is None else np.unique(np.concatenate([self.classes_, classes_arr]))
        return self.fit(X, y)

    def predict(self, X: Sequence[Sequence[float]]) -> np.ndarray:
        if not self._memory:
            raise ValueError("El clasificador no ha sido entrenado todavía")
        default = next(iter(self._memory.values()))
        data = self._standardize(X)
        preds = [self._memory.get(tuple(float(v) for v in sample), default) for sample in data]
        return np.asarray(preds)


class LogisticRegression(_BaseMemorizingClassifier):
    """Implementación mínima que mantiene compatibilidad con la API usada."""

    def __init__(self, *_, **__):
        super().__init__()


class SGDClassifier(_BaseMemorizingClassifier):
    """Versión simplificada del clasificador SGD de scikit-learn."""

    def __init__(self, *_, max_iter: int | None = None, **__):
        super().__init__()
        self.max_iter = max_iter
