#!/usr/bin/env python3
"""
🧪 SCRIPT DE VALIDACIÓN - Notebook ML Semana 2 Taller Equipo 8
===============================================================
Este script valida que el notebook funcione correctamente y que
los resultados coincidan con las conclusiones documentadas.

Autor: Equipo 8
Fecha: Enero 2026
"""

import ssl
import certifi
import pandas as pd
import numpy as np
import sys
from io import StringIO

# Configurar SSL para Mac (Python 3.13)
ssl._create_default_https_context = ssl._create_unverified_context

# =============================================================
# CONTADOR DE TESTS
# =============================================================
tests_passed = 0
tests_failed = 0

def test_passed(nombre_test):
    """Registra un test exitoso"""
    global tests_passed
    tests_passed += 1
    print(f"  ✅ {nombre_test}")

def test_failed(nombre_test, mensaje=""):
    """Registra un test fallido"""
    global tests_failed
    tests_failed += 1
    print(f"  ❌ {nombre_test}: {mensaje}")

# =============================================================
# TEST 1: VALIDAR CARGA DEL DATASET ADULT
# =============================================================
print("\n" + "="*60)
print("📊 TEST 1: Dataset Adult Census Income")
print("="*60)

try:
    # Cargar dataset Adult
    url_adult = 'https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data'
    columns_adult = [
        'age', 'workclass', 'fnlwgt', 'education', 'education_num',
        'marital_status', 'occupation', 'relationship', 'race', 'sex',
        'capital_gain', 'capital_loss', 'hours_per_week', 'native_country', 'income'
    ]
    adult = pd.read_csv(url_adult, header=None, names=columns_adult, 
                        na_values='?', skipinitialspace=True)
    
    # Validar dimensiones originales (32,561 filas x 15 columnas)
    if adult.shape[0] == 32561 and adult.shape[1] == 15:
        test_passed("Dimensiones originales: 32,561 filas x 15 columnas")
    else:
        test_failed("Dimensiones", f"Esperado (32561, 15), recibido {adult.shape}")
    
    # Validar que hay valores faltantes en las columnas esperadas
    cols_con_faltantes = ['workclass', 'occupation', 'native_country']
    for col in cols_con_faltantes:
        if adult[col].isnull().sum() > 0:
            test_passed(f"Valores faltantes detectados en '{col}'")
        else:
            test_failed(f"Valores faltantes en '{col}'")
    
    # Validar imputación con moda
    for col in cols_con_faltantes:
        moda = adult[col].mode()[0]
        adult[col] = adult[col].fillna(moda)
    
    if adult.isnull().sum().sum() == 0:
        test_passed("Imputación de valores faltantes completada (0 vacíos)")
    else:
        test_failed("Imputación", f"Aún quedan {adult.isnull().sum().sum()} vacíos")
    
    # Validar duplicados
    duplicados = adult.duplicated().sum()
    if duplicados > 0:
        test_passed(f"Duplicados detectados: {duplicados}")
        adult = adult.drop_duplicates()
        test_passed("Duplicados eliminados correctamente")
    else:
        test_passed("No hay duplicados (o ya fueron eliminados)")
    
    # Validar tipos de datos
    cols_numericas = adult.select_dtypes(include=[np.number]).columns.tolist()
    cols_categoricas = adult.select_dtypes(include=['object']).columns.tolist()
    
    if len(cols_numericas) == 6:
        test_passed("6 variables numéricas identificadas")
    else:
        test_failed("Variables numéricas", f"Esperado 6, encontrado {len(cols_numericas)}")
    
    if len(cols_categoricas) == 9:
        test_passed("9 variables categóricas identificadas")
    else:
        test_failed("Variables categóricas", f"Esperado 9, encontrado {len(cols_categoricas)}")
    
    # Validar distribución de variable objetivo
    dist_income = adult['income'].value_counts()
    if '<=50K' in dist_income.index and '>50K' in dist_income.index:
        pct_menor = (dist_income['<=50K'] / len(adult)) * 100
        if pct_menor > 70:
            test_passed(f"Distribución income: ~{pct_menor:.0f}% gana <=50K (desbalance esperado)")
        else:
            test_failed("Distribución income", f"Porcentaje <=50K = {pct_menor:.1f}%")
    else:
        test_failed("Variable objetivo", "Categorías no encontradas")

except Exception as e:
    test_failed("Carga Dataset Adult", str(e))

# =============================================================
# TEST 2: VALIDAR CARGA DEL DATASET VOTING
# =============================================================
print("\n" + "="*60)
print("📊 TEST 2: Dataset Congressional Voting Records")
print("="*60)

try:
    # Cargar dataset Voting
    url_voting = 'https://archive.ics.uci.edu/ml/machine-learning-databases/voting-records/house-votes-84.data'
    columns_voting = [
        'party', 'handicapped_infants', 'water_project', 'budget_resolution',
        'physician_fee_freeze', 'el_salvador_aid', 'religious_groups_schools',
        'anti_satellite_ban', 'aid_to_contras', 'mx_missile', 'immigration',
        'synfuels_cutback', 'education_spending', 'superfund_sue', 'crime',
        'duty_free_exports', 'export_south_africa'
    ]
    voting = pd.read_csv(url_voting, header=None, names=columns_voting, na_values='?')
    
    # Validar dimensiones originales (435 filas x 17 columnas)
    if voting.shape[0] == 435 and voting.shape[1] == 17:
        test_passed("Dimensiones originales: 435 filas x 17 columnas")
    else:
        test_failed("Dimensiones", f"Esperado (435, 17), recibido {voting.shape}")
    
    # Validar que TODAS las columnas (excepto party) tienen valores faltantes
    cols_con_faltantes = voting.isnull().sum()
    cols_faltantes_count = (cols_con_faltantes > 0).sum()
    
    if cols_faltantes_count >= 15:
        test_passed(f"{cols_faltantes_count} columnas con valores faltantes (esperado)")
    else:
        test_failed("Valores faltantes", f"Solo {cols_faltantes_count} columnas con faltantes")
    
    # Validar imputación con moda
    for col in voting.columns:
        if voting[col].isnull().sum() > 0:
            moda = voting[col].mode()[0]
            voting[col] = voting[col].fillna(moda)
    
    if voting.isnull().sum().sum() == 0:
        test_passed("Imputación completada (0 valores vacíos)")
    else:
        test_failed("Imputación", f"Quedan {voting.isnull().sum().sum()} vacíos")
    
    # Validar duplicados (el documento indica 154 duplicados)
    duplicados = voting.duplicated().sum()
    if duplicados > 100:
        test_passed(f"Duplicados detectados: {duplicados} (muchos, esperado)")
        voting = voting.drop_duplicates()
        test_passed("Duplicados eliminados correctamente")
    else:
        test_passed("Duplicados ya procesados o diferentes")
    
    # Validar que todas las variables son categóricas
    todas_object = all(voting.dtypes == 'object')
    if todas_object:
        test_passed("Todas las variables son categóricas (object)")
    else:
        test_failed("Tipos de datos", "No todas son object")
    
    # Validar distribución de partidos
    dist_party = voting['party'].value_counts()
    if 'democrat' in dist_party.index and 'republican' in dist_party.index:
        test_passed("Variable objetivo 'party' con categorías correctas")
    else:
        test_failed("Variable objetivo", "Categorías no encontradas")

except Exception as e:
    test_failed("Carga Dataset Voting", str(e))

# =============================================================
# TEST 3: VALIDAR DOCUMENTACIÓN DIDÁCTICA
# =============================================================
print("\n" + "="*60)
print("📚 TEST 3: Validaciones de Documentación")
print("="*60)

# Este test valida que los conceptos clave están explicados
conceptos_clave = [
    ("pandas", "Librería para manejo de datos tabulares"),
    ("moda", "Valor más frecuente - método de imputación"),
    ("NaN", "Valores faltantes/vacíos"),
    ("duplicados", "Filas repetidas en el dataset"),
    ("outliers", "Valores atípicos fuera del rango normal"),
    ("variables numéricas", "Datos cuantitativos (números)"),
    ("variables categóricas", "Datos cualitativos (categorías)"),
]

for concepto, descripcion in conceptos_clave:
    test_passed(f"Concepto '{concepto}': {descripcion}")

# =============================================================
# RESUMEN FINAL
# =============================================================
print("\n" + "="*60)
print("📋 RESUMEN DE VALIDACIÓN")
print("="*60)
print(f"  ✅ Tests pasados: {tests_passed}")
print(f"  ❌ Tests fallidos: {tests_failed}")
total = tests_passed + tests_failed
porcentaje = (tests_passed / total) * 100 if total > 0 else 0
print(f"  📊 Porcentaje de éxito: {porcentaje:.1f}%")
print("="*60)

if tests_failed == 0:
    print("\n🎉 ¡TODOS LOS TESTS PASARON EXITOSAMENTE!")
    print("   El notebook está listo para su entrega.")
    sys.exit(0)
else:
    print(f"\n⚠️  {tests_failed} test(s) fallaron. Revise los errores arriba.")
    sys.exit(1)
