"""
smart_clean.py  — EssentiaX Clean Pro
=================================================
Universal ML-ready Data Cleaner
• Intelligent missing value handling
• Robust outlier removal
• Safe scaling
• High-quality encoding
• Dataset insights + warnings
"""

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline


def smart_clean(
    df: pd.DataFrame,
    missing_strategy: str = "auto",     # auto | mean | median | mode
    outlier_strategy: str = "iqr",      # iqr | none
    scale_numeric: bool = True,
    encode_categorical: bool = True,
    inplace: bool = False,
    verbose: bool = True
) -> pd.DataFrame:

    if not inplace:
        df = df.copy()

    if verbose:
        print("\n🧹 **EssentiaX Clean Pro: Starting Cleaning Pipeline...**")
        print("------------------------------------------------------------")

    # Column Types
    numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
    categorical_cols = df.select_dtypes(include=["object", "category"]).columns.tolist()

    if verbose:
        print(f"🔍 Detected {len(numeric_cols)} numeric columns")
        print(f"🔍 Detected {len(categorical_cols)} categorical columns")

    # === 1️⃣ MISSING VALUE HANDLING ===================================
    if verbose:
        print("\n1️⃣ Handling Missing Values...")

    missing_info = df.isna().sum()
    total_missing = missing_info.sum()

    if total_missing == 0:
        if verbose:
            print("   ✅ No missing values found.")
    else:
        if verbose:
            print(f"   ⚠ Found {total_missing} missing values.")

        # Strategy Auto Decide
        for col in df.columns:
            if df[col].isna().sum() == 0:
                continue

            if missing_strategy == "auto":
                if col in numeric_cols:
                    strategy = "median"
                else:
                    strategy = "mode"
            else:
                strategy = missing_strategy

            if strategy == "mean" and col in numeric_cols:
                df[col] = df[col].fillna(df[col].mean())
            elif strategy == "median" and col in numeric_cols:
                df[col] = df[col].fillna(df[col].median())
            else:  # mode for categorical
                df[col] = df[col].fillna(df[col].mode().iloc[0])

        if verbose:
            print(f"   ✅ Missing values handled using '{strategy}' strategy.")

    # === 2️⃣ OUTLIER REMOVAL ==========================================
    if outlier_strategy == "iqr" and len(numeric_cols) > 0:

        if verbose:
            print("\n2️⃣ Removing Outliers (IQR Method)...")

        before_rows = df.shape[0]

        for col in numeric_cols:
            Q1 = df[col].quantile(0.25)
            Q3 = df[col].quantile(0.75)
            IQR = Q3 - Q1
            if IQR == 0:
                continue
            lower = Q1 - 1.5 * IQR
            upper = Q3 + 1.5 * IQR
            df = df[(df[col] >= lower) & (df[col] <= upper)]

        removed = before_rows - df.shape[0]
        if verbose:
            print(f"   🧮 Removed {removed} rows as outliers.")
    else:
        if verbose:
            print("\n2️⃣ Outlier Removal Skipped.")

    # === 3️⃣ SCALING ================================================
    if scale_numeric and len(numeric_cols) > 0:
        if verbose:
            print("\n3️⃣ Scaling Numeric Features (StandardScaler)...")

        scaler = StandardScaler()
        df[numeric_cols] = scaler.fit_transform(df[numeric_cols])

        if verbose:
            print(f"   ✅ Scaled {len(numeric_cols)} numeric features.")
    else:
        if verbose:
            print("\n3️⃣ Scaling Skipped.")

    # === 4️⃣ ENCODING ===============================================
    if encode_categorical and len(categorical_cols) > 0:
        if verbose:
            print("\n4️⃣ Encoding Categorical Features (OneHotEncoder)...")

        ohe = OneHotEncoder(sparse_output=False, handle_unknown="ignore")
        encoded = ohe.fit_transform(df[categorical_cols])

        encoded_df = pd.DataFrame(
            encoded,
            columns=ohe.get_feature_names_out(categorical_cols),
            index=df.index
        )

        df.drop(columns=categorical_cols, inplace=True)
        df = pd.concat([df, encoded_df], axis=1)

        if verbose:
            print(f"   ✅ Encoded {len(categorical_cols)} categorical columns.")
    else:
        if verbose:
            print("\n4️⃣ Encoding Skipped.")

    # === 5️⃣ SUMMARY ================================================
    if verbose:
        print("\n------------------------------------------------------------")
        print("🎯 **CLEANING SUMMARY**")
        print(f"🧾 Final shape: {df.shape}")
        print(f"📦 Memory usage: {df.memory_usage(deep=True).sum() / 1024**2:.2f} MB")

        # High-level insights
        print("\n📌 Insights:")
        if total_missing > 0:
            print("   • Missing values have been handled safely.")
        if outlier_strategy == "iqr":
            print("   • Outliers removed using robust IQR boundaries.")
        if scale_numeric:
            print("   • Numeric features scaled (ready for ML models).")
        if encode_categorical:
            print("   • Categorical features encoded (one-hot).")

        # Warnings
        print("\n⚠ Potential issues you may want to check:")
        if len(categorical_cols) > 15:
            print("   • High-cardinality categorical columns (may cause huge feature explosion).")
        if len(numeric_cols) == 0:
            print("   • No numeric columns detected — ML may be limited.")
        if len(df.columns) > 3000:
            print("   • Extremely large feature space after encoding — consider dimensionality reduction.")

        print("------------------------------------------------------------")
        print("✨ **EssentiaX Clean Pro Completed!**\n")

    return df
