"""
Classe principale Profiler — orchestratore del package.

Il Profiler compone reader, analyzer ed exporter per eseguire
il profiling completo di un dataset e generare report.

Questo modulo dimostra il pattern Composizione: il Profiler
non eredita da nessuna classe, ma utilizza istanze di reader,
analyzer ed exporter per svolgere il suo compito.
"""

import os

from dataprofiler.readers import ReaderFactory
from dataprofiler.analyzers import (
    NumericAnalyzer,
    TextAnalyzer,
    DateTimeAnalyzer,
    BooleanAnalyzer,
)
from dataprofiler.analyzers.base import BaseAnalyzer
from dataprofiler.exporters import ExporterFactory
from dataprofiler.models.dataset_profile import DatasetProfile
from dataprofiler.models.column_profile import ColumnProfile


class Profiler:
    """Classe principale per il profiling di dataset.

    Orchestratore che compone un reader (per leggere i dati),
    una lista di analyzer (per analizzare le colonne) e un
    exporter (per generare il report).

    Dimostra i seguenti pattern OOP:
        - Composizione (usa reader, analyzer, exporter)
        - Polimorfismo (gli analyzer sono intercambiabili)
        - Factory (usa ReaderFactory e ExporterFactory)

    Args:
        filepath: Percorso del file da analizzare.
        **reader_kwargs: Parametri aggiuntivi per il reader
            (es. delimiter=";", encoding="latin-1").

    Esempio::

        # Uso base
        profiler = Profiler("dati.csv")
        report = profiler.profile()
        profiler.export(report, "report.md")

        # Con opzioni per il reader
        profiler = Profiler("dati_it.csv", delimiter=";", encoding="latin-1")
        report = profiler.profile()

        # Riepilogo rapido
        print(profiler.quick_summary())
    """

    def __init__(self, filepath: str, **reader_kwargs):
        self._filepath = filepath
        self._reader = ReaderFactory.create(filepath, **reader_kwargs)

        # Lista degli analyzer, in ordine di priorità.
        # Il primo che risponde True a can_analyze() viene utilizzato.
        # L'ordine è importante: BooleanAnalyzer prima di TextAnalyzer
        # perché TextAnalyzer è il fallback generico.
        self._analyzers: list[BaseAnalyzer] = [
            BooleanAnalyzer(),
            NumericAnalyzer(),
            DateTimeAnalyzer(),
            TextAnalyzer(),  # Fallback — accetta tutto
        ]

        self._data: list[dict] = []

    def profile(self) -> DatasetProfile:
        """Esegue il profiling completo del dataset.

        Fasi:
            1. Lettura dati tramite il reader
            2. Creazione del profilo base del dataset
            3. Analisi colonna per colonna con gli analyzer
            4. Generazione di warnings su potenziali problemi

        Returns:
            DatasetProfile con tutte le statistiche calcolate.
        """
        # 1. Lettura dati
        self._data = self._reader.read()
        headers = self._reader.get_headers()

        # 2. Creazione profilo base
        dataset = DatasetProfile(
            filename=os.path.basename(self._filepath),
            row_count=len(self._data),
            column_count=len(headers),
            file_size_bytes=self._reader.file_size,
        )

        # 3. Analisi colonna per colonna
        for header in headers:
            values = [row.get(header) for row in self._data]
            col_profile = self._analyze_column(header, values)
            dataset.columns.append(col_profile)

        # 4. Generazione warnings
        self._generate_warnings(dataset)

        return dataset

    def _analyze_column(self, name: str, values: list) -> ColumnProfile:
        """Trova l'analyzer appropriato e analizza la colonna.

        Itera sugli analyzer registrati e usa il primo che
        risponde True a can_analyze(). Questo è il polimorfismo
        in azione: ogni analyzer decide autonomamente se può
        gestire la colonna.

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

        Returns:
            ColumnProfile con le statistiche calcolate.
        """
        for analyzer in self._analyzers:
            if analyzer.can_analyze(values, name):
                return analyzer.analyze(values, name)

        # Fallback sicuro (non dovrebbe mai arrivarci grazie a TextAnalyzer)
        return TextAnalyzer().analyze(values, name)

    def _generate_warnings(self, profile: DatasetProfile) -> None:
        """Genera avvisi automatici su potenziali problemi nei dati.

        Controlla:
            - Colonne con troppi valori mancanti (>50%)
            - Colonne con un solo valore unico (costanti)
            - Colonne dove tutti i valori sono unici (possibili ID)
            - Dataset completamente vuoto

        Args:
            profile: Il profilo del dataset da verificare.
        """
        if profile.row_count == 0:
            profile.warnings.append("Il dataset è vuoto (0 righe)")
            return

        for col in profile.columns:
            if col.missing_percentage > 50:
                profile.warnings.append(
                    f"Colonna '{col.name}': {col.missing_percentage:.0f}% "
                    f"di valori mancanti"
                )
            if col.unique_count == 1 and col.total_count > 1:
                profile.warnings.append(
                    f"Colonna '{col.name}': contiene un solo valore unico "
                    f"(valore costante)"
                )
            if (
                col.unique_count == col.total_count - col.missing_count
                and col.total_count > 10
                and col.missing_count == 0
            ):
                profile.warnings.append(
                    f"Colonna '{col.name}': tutti i valori sono unici "
                    f"(possibile colonna ID)"
                )

    def export(
        self,
        profile: DatasetProfile,
        output_path: str,
        fmt: str = "md",
    ) -> None:
        """Esporta il profilo nel formato richiesto.

        Usa la ExporterFactory per creare l'exporter appropriato
        (pattern Strategy: l'exporter è intercambiabile).

        Args:
            profile: Il profilo del dataset da esportare.
            output_path: Percorso del file di output.
            fmt: Formato di output ("md", "json", "html").
        """
        exporter = ExporterFactory.create(fmt)
        exporter.export(profile, output_path)

    def quick_summary(self) -> str:
        """Esegue il profiling e restituisce un riepilogo testuale.

        Metodo di convenienza per ottenere rapidamente
        un riepilogo del dataset senza dover gestire
        il DatasetProfile.

        Returns:
            Stringa con il riepilogo del dataset.
        """
        profile = self.profile()
        summary = profile.summary()
        lines = [
            f"═══ DataProfiler — Riepilogo ═══",
            f"",
            f"  File:        {summary['file']}",
            f"  Dimensioni:  {summary['righe']:,} righe × {summary['colonne']} colonne",
            f"  Completezza: {summary['completezza']}",
            f"  Mancanti:    {summary['valori_mancanti']:,} valori",
            f"  Avvisi:      {summary['warnings']}",
            f"",
        ]

        # Dettaglio colonne
        lines.append("  Colonne:")
        for col in profile.columns:
            lines.append(
                f"    • {col.name:<20s} {col.dtype.value:<10s} "
                f"{col.completeness:>5.1f}% completo"
            )

        return "\n".join(lines)
