import os
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, Dataset
from torchvision import models, transforms
from PIL import Image
import numpy as np
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.manifold import TSNE
import pandas as pd
import matplotlib.pyplot as plt

# 1. CONFIGURACIÓN
IMG_DIR = "datos_para_ai"
OUTPUT_DIR = "pruebas_clusters"
N_CLUSTERS = 10 
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")

os.makedirs(OUTPUT_DIR, exist_ok=True)

# 2. CARGAR MODELO PRE-ENTRENADO (Directo de PyTorch)
def get_pretrained_extractor():
    # Usamos los pesos por defecto de ImageNet
    model = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.DEFAULT)
    model.classifier = nn.Identity() # Quitamos la capa de clasificación
    model.to(DEVICE)
    model.eval()
    return model

# 3. DATASET CON NORMALIZACIÓN ESTÁNDAR
class MSIDatasetDirect(Dataset):
    def __init__(self, img_dir):
        self.img_dir = img_dir
        self.img_names = sorted([f for f in os.listdir(img_dir) if f.endswith('.png')])
        self.transform = transforms.Compose([
            transforms.Resize((224, 224)),
            transforms.ToTensor(),
            # Normalización obligatoria para modelos pre-entrenados
            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
        ])

    def __len__(self):
        return len(self.img_names)

    def __getitem__(self, idx):
        img_path = os.path.join(self.img_dir, self.img_names[idx])
        image = Image.open(img_path).convert("RGB")
        return self.transform(image), self.img_names[idx]

if __name__ == "__main__":
    print(f"Extrayendo características con red pre-entrenada en {DEVICE}...")
    model = get_pretrained_extractor()
    dataset = MSIDatasetDirect(IMG_DIR)
    loader = DataLoader(dataset, batch_size=1, shuffle=False)

    features = []
    ion_names = []

    with torch.no_grad():
        for img, name in loader:
            img = img.to(DEVICE)
            feat = model(img)
            # Aplanar el vector de características (1280 dimensiones para EffNet-B0)
            features.append(feat.cpu().numpy().flatten())
            ion_names.append(name[0])

    features = np.array(features)
    print(f"Características extraídas: {features.shape}")

    # 4. REDUCCIÓN DE DIMENSIONALIDAD (PCA)
    # Esto ayuda a que el clustering no se pierda en dimensiones irrelevantes
    print("Reduciendo ruido con PCA...")
    features_scaled = StandardScaler().fit_transform(features)
    pca = PCA(n_components=0.95) # Mantenemos el 95% de la varianza
    features_pca = pca.fit_transform(features_scaled)
    print(f"Dimensiones tras PCA: {features_pca.shape[1]}")

    # 5. CLUSTERING K-MEANS
    print(f"Agrupando en {N_CLUSTERS} clusters...")
    km = KMeans(n_clusters=N_CLUSTERS, n_init=20, random_state=42)
    labels = km.fit_predict(features_pca)

    # 6. VISUALIZACIÓN (t-SNE)
    print("Generando visualización t-SNE para verificar consistencia...")
    tsne = TSNE(n_components=2, random_state=42)
    features_tsne = tsne.fit_transform(features_pca)
    
    plt.figure(figsize=(10, 8))
    scatter = plt.scatter(features_tsne[:, 0], features_tsne[:, 1], c=labels, cmap='tab10', alpha=0.7)
    plt.colorbar(scatter, ticks=range(N_CLUSTERS), label='Cluster ID')
    plt.title(f"Visualización de Clusters (K={N_CLUSTERS}) - Directo")
    plt.xlabel("t-SNE 1")
    plt.ylabel("t-SNE 2")
    plt.grid(True, linestyle='--', alpha=0.5)
    plt.savefig(os.path.join(OUTPUT_DIR, "msi_clusters_direct_plot.png"))
    print(f"Gráfico de consistencia guardado en '{OUTPUT_DIR}/msi_clusters_direct_plot.png'.")

    # 7. GUARDAR Y REPORTAR
    df = pd.DataFrame({'ion_image': ion_names, 'cluster': labels})
    df['mz'] = df['ion_image'].str.extract(r'(\d+\.\d+)').astype(float)
    df.to_csv(os.path.join(OUTPUT_DIR, "msi_clusters_direct_results.csv"), index=False)
    
    # Crear un resumen de cuántos iones hay por cluster
    print("\nResumen de Clusters (Consistente con el CSV y el Gráfico):")
    counts = df['cluster'].value_counts().sort_index()
    print(counts)
    
    # Guardar también el resumen para evitar confusiones con otros archivos
    summary_df = counts.reset_index()
    summary_df.columns = ['Cluster_ID', 'Ion_Count']
    summary_df.to_csv(os.path.join(OUTPUT_DIR, "msi_clusters_direct_summary.csv"), index=False)
    
    print(f"\nResultados guardados en '{OUTPUT_DIR}/msi_clusters_direct_results.csv' y '{OUTPUT_DIR}/msi_clusters_direct_summary.csv'.")
    print(f"Usa '{OUTPUT_DIR}/msi_clusters_direct_plot.png' para validar visualmente los puntos.")
