"""
Analyzer per colonne con dati numerici.

Calcola statistiche descrittive: media, mediana, deviazione standard,
quartili, minimo, massimo, conteggio zeri e valori negativi.
"""

from .base import BaseAnalyzer
from dataprofiler.models.column_profile import ColumnProfile, ColumnType
from dataprofiler.utils import is_numeric


class NumericAnalyzer(BaseAnalyzer):
    """Analizza colonne contenenti dati numerici.

    Rileva una colonna come numerica se almeno il 70% dei valori
    non-null sono convertibili a float.

    Statistiche calcolate:
        - min, max
        - media, mediana
        - deviazione standard (std)
        - primo e terzo quartile (q1, q3)
        - conteggio di zeri e valori negativi

    Esempio::

        analyzer = NumericAnalyzer()
        values = ["10", "20", "30", None, "40"]
        if analyzer.can_analyze(values, "prezzo"):
            profile = analyzer.analyze(values, "prezzo")
            print(profile.stats["media"])  # 25.0
    """

    NUMERIC_THRESHOLD = 0.70  # 70% dei valori devono essere numerici

    def can_analyze(self, values: list, column_name: str) -> bool:
        """Verifica se almeno il 70% dei valori non-null sono numerici.

        Args:
            values: Lista dei valori della colonna.
            column_name: Nome della colonna (non utilizzato).

        Returns:
            True se la colonna è prevalentemente numerica.
        """
        non_null = self._get_non_null(values)
        if not non_null:
            return False
        numeric_count = sum(1 for v in non_null if is_numeric(v))
        return (numeric_count / len(non_null)) >= self.NUMERIC_THRESHOLD

    def analyze(self, values: list, column_name: str) -> ColumnProfile:
        """Calcola statistiche numeriche sulla colonna.

        Args:
            values: Lista dei valori della colonna.
            column_name: Nome della colonna.

        Returns:
            ColumnProfile con tipo NUMERIC e statistiche calcolate.
        """
        numeric_vals: list[float] = []
        missing = 0

        for v in values:
            if v is None or (isinstance(v, str) and v.strip() == ""):
                missing += 1
            elif is_numeric(v):
                numeric_vals.append(float(v))
            else:
                missing += 1

        stats: dict = {}
        if numeric_vals:
            sorted_vals = sorted(numeric_vals)
            n = len(sorted_vals)
            mean = sum(sorted_vals) / n

            stats = {
                "min": sorted_vals[0],
                "max": sorted_vals[-1],
                "media": mean,
                "mediana": self._median(sorted_vals),
                "std": self._std(sorted_vals, mean),
                "q1": self._percentile(sorted_vals, 25),
                "q3": self._percentile(sorted_vals, 75),
                "somma": sum(sorted_vals),
                "zeros": sum(1 for v in sorted_vals if v == 0),
                "negativi": sum(1 for v in sorted_vals if v < 0),
            }

        return ColumnProfile(
            name=column_name,
            dtype=ColumnType.NUMERIC,
            total_count=len(values),
            missing_count=missing,
            unique_count=len(set(numeric_vals)),
            stats=stats,
        )

    @staticmethod
    def _median(sorted_vals: list[float]) -> float:
        """Calcola la mediana di una lista ordinata."""
        n = len(sorted_vals)
        mid = n // 2
        if n % 2 == 0:
            return (sorted_vals[mid - 1] + sorted_vals[mid]) / 2
        return sorted_vals[mid]

    @staticmethod
    def _percentile(sorted_vals: list[float], p: int) -> float:
        """Calcola il percentile p-esimo usando interpolazione lineare."""
        n = len(sorted_vals)
        if n == 1:
            return sorted_vals[0]
        k = (n - 1) * (p / 100)
        f = int(k)
        c = min(f + 1, n - 1)
        return sorted_vals[f] + (k - f) * (sorted_vals[c] - sorted_vals[f])

    @staticmethod
    def _std(values: list[float], mean: float) -> float:
        """Calcola la deviazione standard della popolazione."""
        if len(values) < 2:
            return 0.0
        variance = sum((x - mean) ** 2 for x in values) / len(values)
        return variance ** 0.5
