import logging
from typing import Optional, List
from .preprocessing import AutoCleaner, SmartImputer, AdaptiveEncoder
from .analysis import InsightEngine  # Importar para type hinting


# Adicione a importação no topo
from .preprocessing import AutoCleaner, SmartImputer, AdaptiveEncoder, DateFeaturizer


class CodeSynthesizer:
    def __init__(
        self,
        cleaner: AutoCleaner,
        imputer: SmartImputer,
        encoder: AdaptiveEncoder,
        date_featurizer: Optional[DateFeaturizer] = None,  # <--- NOVO
        insight_engine: Optional[InsightEngine] = None,
    ):
        self.cleaner = cleaner
        self.imputer = imputer
        self.encoder = encoder
        self.date_featurizer = date_featurizer  # <--- NOVO
        self.insight = insight_engine

    def export_to_script(self, filepath: str = "pipeline_production.py"):
        lines = []
        lines.append(
            '"""\nScoutML Generated Pipeline\nReady for Databricks/Pandas.\n"""'
        )
        lines.append("import pandas as pd")
        lines.append("import numpy as np")
        lines.append("")
        lines.append("def run_pipeline(df: pd.DataFrame) -> pd.DataFrame:")
        lines.append("    df = df.copy()")
        lines.append("")

        # ---------------------------------------------------------
        # 0. Date Featurizer (DEVE VIR PRIMEIRO)
        # ---------------------------------------------------------
        if self.date_featurizer and self.date_featurizer.date_cols_:
            lines.append("    # [0] DateFeaturizer: Extracting time features")
            lines.append(f"    date_cols = {self.date_featurizer.date_cols_}")
            lines.append("    for col in date_cols:")
            lines.append("        if col in df.columns:")
            lines.append("            # Force datetime conversion")

            # Injeta o formato se ele existir, senão deixa o pandas adivinhar
            if self.date_featurizer.date_format:
                fmt = self.date_featurizer.date_format
                lines.append(
                    f"            series = pd.to_datetime(df[col], format='{fmt}', errors='coerce')"
                )
            else:
                lines.append(
                    "            series = pd.to_datetime(df[col], errors='coerce')"
                )

            lines.append("            df[f'{col}_year'] = series.dt.year")
            lines.append("            df[f'{col}_month'] = series.dt.month")
            lines.append("            df[f'{col}_day'] = series.dt.day")
            lines.append("            df[f'{col}_dayofweek'] = series.dt.dayofweek")
            lines.append(
                "            df[f'{col}_is_weekend'] = (series.dt.dayofweek >= 5).astype(int)"
            )
            lines.append("            df.drop(columns=[col], inplace=True)")
            lines.append("")

        # ---------------------------------------------------------
        # 1. Cleaner & Insight Drops
        # ---------------------------------------------------------
        # Combina drops do AutoCleaner (técnico) com InsightEngine (estatístico)
        all_drops = set(self.cleaner.cols_to_drop_)

        if self.insight:
            if self.insight.leakage_candidates_:
                all_drops.update(self.insight.leakage_candidates_)
            if self.insight.useless_features_:
                all_drops.update(self.insight.useless_features_)

        drops_list = list(all_drops)

        if drops_list:
            lines.append(f"    # [1] AutoCleaner & InsightEngine Drops")
            lines.append(f"    # Drops: IDs, Constants, Leakage, Useless Features")
            lines.append(f"    drop_cols = {drops_list}")
            lines.append(
                "    df.drop(columns=drop_cols, errors='ignore', inplace=True)"
            )
            lines.append("")

        # ... (Resto do código igual: Imputer, Encoder, etc.) ...
        # (Copie o restante da lógica do imputer e encoder do arquivo anterior aqui)

        # MANTENHA O RESTO DO CÓDIGO DO IMPUTER E ENCODER AQUI
        # ...

        # ---------------------------------------------------------
        # 2. Imputer
        # ---------------------------------------------------------
        lines.append("    # [2] SmartImputer")
        if self.imputer.missing_indicators_:
            lines.append(f"    # Flagging structural missingness")
            lines.append(f"    missing_flags = {self.imputer.missing_indicators_}")
            lines.append("    for col in missing_flags:")
            lines.append("        if col in df.columns:")
            lines.append(
                f"            df[f'is_missing_{{col}}'] = df[col].isnull().astype(int)"
            )

        if self.imputer.imputation_map_:
            lines.append("    # Applying Fillna (Median/Mode)")
            lines.append(f"    fill_values = {self.imputer.imputation_map_}")
            lines.append("    df.fillna(value=fill_values, inplace=True)")
        lines.append("")

        # ---------------------------------------------------------
        # 3. Encoder (Fuzzy -> Rare -> Encode)
        # ---------------------------------------------------------
        lines.append("    # [3] AdaptiveEncoder")

        if self.encoder.fuzzy_maps_:
            lines.append("    # [3.1] Fuzzy Matching Correction")
            for col, f_map in self.encoder.fuzzy_maps_.items():
                lines.append(f"    if '{col}' in df.columns:")
                lines.append(f"        f_map_{col} = {f_map}")
                lines.append(f"        df['{col}'] = df['{col}'].replace(f_map_{col})")
            lines.append("")

        if self.encoder.rare_groups_:
            lines.append("    # [3.2] Rare Label Grouping (-> 'OTHER')")
            for col, kept_vals in self.encoder.rare_groups_.items():
                lines.append(f"    if '{col}' in df.columns:")
                lines.append(f"        kept_{col} = {kept_vals}")
                lines.append(
                    f"        df['{col}'] = np.where(df['{col}'].isin(kept_{col}), df['{col}'], 'OTHER')"
                )
            lines.append("")

        if self.encoder.encoding_map_:
            lines.append("    # [3.3] Target Encoding (Smoothed Globals)")
            for col, mapping in self.encoder.encoding_map_.items():
                fallback = self.encoder.global_means_.get(col, 0)
                lines.append(f"    if '{col}' in df.columns:")
                lines.append(f"        map_{col} = {str(mapping)}")
                lines.append(
                    f"        df['{col}'] = df['{col}'].map(map_{col}).fillna({fallback})"
                )
            lines.append("")

        if self.encoder.ohe_cols_:
            lines.append(f"    # [3.4] One-Hot Encoding")
            lines.append(f"    ohe_cols = {self.encoder.ohe_cols_}")
            lines.append("    # Ensure columns exist before OHE to avoid errors")
            lines.append("    valid_ohe = [c for c in ohe_cols if c in df.columns]")
            lines.append(
                "    df = pd.get_dummies(df, columns=valid_ohe, drop_first=True)"
            )

        lines.append("")
        lines.append("    return df")

        try:
            with open(filepath, "w", encoding="utf-8") as f:
                f.write("\n".join(lines))
            print(f"Pipeline exportado com sucesso para: {filepath}")
        except Exception as e:
            print(f"Erro ao exportar pipeline: {e}")
