import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, davies_bouldin_score, calinski_harabasz_score
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
from sklearn.model_selection import KFold
from yellowbrick.cluster import KElbowVisualizer
from sklearn.utils import resample
import warnings

warnings.filterwarnings('ignore', category=UserWarning)

# --- CARGAR Y PREPROCESAR DATOS ---
def load_and_preprocess(filepath):
    df = pd.read_csv(filepath)
    df = df[df['course_id'] != 2122130159]  # Eliminar outlier

    exclude_fields = ['course_id', 'category', 'sortorder', 'idnumber', 'category_id']
    features = [col for col in df.select_dtypes(include=[np.number]).columns
                if col not in exclude_fields]

    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(df[features])

    return df, X_scaled, features

# --- FUNCIÓN DE ESTABILIDAD CORREGIDA ---
def calculate_cluster_stability(X, n_clusters=5, n_iter=10):
    jaccard_scores = []
    for _ in range(n_iter):
        sample_size = min(1000, len(X))
        X_sample = resample(X, replace=True, n_samples=sample_size)

        kmeans1 = KMeans(n_clusters=n_clusters, n_init=10, random_state=np.random.randint(1000))
        labels1 = kmeans1.fit_predict(X_sample)

        kmeans2 = KMeans(n_clusters=n_clusters, n_init=10, random_state=np.random.randint(1000))
        labels2 = kmeans2.fit_predict(X_sample)

        intersection = np.sum(labels1 == labels2)
        union = len(labels1)
        jaccard_scores.append(intersection / union if union > 0 else 0)

    return np.mean(jaccard_scores) if jaccard_scores else 0

# --- VALIDACIÓN CRUZADA CORREGIDA ---
def cross_validate_clusters(X, n_clusters=5, n_splits=3):
    kf = KFold(n_splits=n_splits, shuffle=True, random_state=42)
    stability_scores = []

    min_samples_per_split = n_clusters * 2
    if len(X) < min_samples_per_split * n_splits:
        n_splits = max(2, len(X) // min_samples_per_split)
        kf = KFold(n_splits=n_splits, shuffle=True, random_state=42)

    for train_idx, test_idx in kf.split(X):
        X_train, X_test = X[train_idx], X[test_idx]

        if len(X_train) < min_samples_per_split or len(X_test) < min_samples_per_split:
            continue

        kmeans = KMeans(n_clusters=n_clusters, n_init=10, random_state=42)
        train_labels = kmeans.fit_predict(X_train)

        if len(X_test) < n_clusters:
            continue

        test_labels = kmeans.predict(X_test)

        if len(np.unique(test_labels)) > 1:
            stability_scores.append(silhouette_score(X_test, test_labels))

    return np.mean(stability_scores) if stability_scores else 0

# --- VISUALIZACIÓN EN 2D ---
def plot_clusters_2d(X, labels, title="Clusters", method="PCA"):
    if method == "PCA":
        model = PCA(n_components=2)
        X_2d = model.fit_transform(X)
        print(f"PCA - Varianza explicada: {model.explained_variance_ratio_.sum():.2%}")
    elif method == "t-SNE":
        model = TSNE(n_components=2, random_state=42, perplexity=min(30, len(X)-1))
        X_2d = model.fit_transform(X)
    else:
        raise ValueError("Método no soportado. Use 'PCA' o 't-SNE'.")

    plt.figure(figsize=(8, 6))
    plt.scatter(X_2d[:, 0], X_2d[:, 1], c=labels, cmap='tab10', s=10, alpha=0.6)
    plt.title(f'{title} ({method})')
    plt.xlabel('Dimensión 1')
    plt.ylabel('Dimensión 2')
    plt.colorbar(label='Cluster')
    plt.grid(True)
    plt.show()

# --- PIPELINE PRINCIPAL ACTUALIZADO ---
def robust_cluster_analysis(filepath):
    try:
        df, X_scaled, features = load_and_preprocess(filepath)
        max_clusters = min(10, len(X_scaled)//2)
        k_range = range(2, max(3, max_clusters))

        print(f"\n=== ANÁLISIS CON {len(X_scaled)} MUESTRAS Y {len(features)} CARACTERÍSTICAS ===")
        print(f"Rango de clusters evaluado: {list(k_range)}")

        # --- MÉTRICAS BÁSICAS ---
        print("\n=== MÉTRICAS BÁSICAS ===")
        try:
            visualizer = KElbowVisualizer(KMeans(random_state=42), k=k_range)
            visualizer.fit(X_scaled)
            visualizer.show()
        except Exception as e:
            print(f"Advertencia en método del codo: {str(e)}")

        # --- ANÁLISIS DE SENSIBILIDAD ---
        print("\n=== ANÁLISIS DE SENSIBILIDAD ===")
        sensitivity_results = pd.DataFrame(index=k_range)

        for k in k_range:
            kmeans = KMeans(n_clusters=k, n_init=10, random_state=42)
            labels = kmeans.fit_predict(X_scaled)

            sensitivity_results.loc[k, 'Silhouette'] = silhouette_score(X_scaled, labels)
            sensitivity_results.loc[k, 'Davies-Bouldin'] = davies_bouldin_score(X_scaled, labels)
            sensitivity_results.loc[k, 'Calinski-Harabasz'] = calinski_harabasz_score(X_scaled, labels)
            sensitivity_results.loc[k, 'Estabilidad'] = calculate_cluster_stability(X_scaled, k)

        plt.figure(figsize=(12, 6))
        for col in sensitivity_results.columns:
            plt.plot(sensitivity_results.index, sensitivity_results[col], marker='o', label=col)
        plt.title('Métricas por Número de Clústeres')
        plt.xlabel('Número de Clústeres')
        plt.ylabel('Valor')
        plt.legend()
        plt.grid()
        plt.show()

        # --- VALIDACIÓN CRUZADA ---
        print("\n=== ESTABILIDAD ===")
        stability = {k: cross_validate_clusters(X_scaled, k) for k in k_range}
        print("Estabilidad por número de clusters:")
        for k, score in stability.items():
            print(f"Clusters {k}: {score:.3f}")

        # --- SELECCIÓN DEL NÚMERO ÓPTIMO ---
        optimal_k = int(input("\nIngrese el número óptimo de clústeres: "))

        # --- CLUSTERING FINAL ---
        kmeans = KMeans(n_clusters=optimal_k, n_init=10, random_state=42)
        df['Cluster'] = kmeans.fit_predict(X_scaled)

        print("\n=== DISTRIBUCIÓN DE CLÚSTERES ===")
        print(df['Cluster'].value_counts().sort_index())

        # --- VISUALIZACIONES EN 2D ---
        print("\n=== VISUALIZACIÓN DE CLÚSTERES ===")
        plot_clusters_2d(X_scaled, df['Cluster'].values, title="Clusters", method="PCA")
        plot_clusters_2d(X_scaled, df['Cluster'].values, title="Clusters", method="t-SNE")

        # --- GUARDAR RESULTADOS ---
        df.to_csv('Resultados_Clustering.csv', index=False)
        print("\nAnálisis completado. Resultados guardados en 'Resultados_Clustering.csv'")

        return df

    except Exception as e:
        print(f"\nERROR: {str(e)}")
        return None

# Ejecución segura
df_resultados = robust_cluster_analysis('/content/TMP/Resultado/Curso_2021.csv')