import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import MinMaxScaler

# ==============================================================================
# 0. CRIAÇÃO DO DATAFRAME DIDÁTICO
# ==============================================================================
# Criamos uma base com problemas específicos para ver cada tratamento em ação.
dados = {
    "cpf": [
        "111",
        "222",
        "111",
        "333",
        "444",
        "555",
        "666",
    ],  # 111 está duplicado
    "data_nascimento": [
        "1990-01-01",
        "1985-05-12",
        "1990-01-01",
        "1995-07-20",
        "2000-03-15",
        "1980-11-30",
        "1975-08-25",
    ],
    "cidade": [
        "São Paulo",
        " curitiba ",
        "SÃO PAULO",
        "Rio de Janeiro",
        "RIO DE JANEIRO",
        "Belo Horizonte",
        "Brasília",
    ],
    "pais": ["BR", "Brazil", "BRA", "Argentina", "Brasil", "BR", "Brasil"],
    "data_inicio": [
        "2026-01-01",
        "2026-02-10",
        "2026-01-01",
        "2026-03-20",
        "2026-04-01",
        "2026-05-10",
        "2026-06-01",
    ],
    "data_fim": [
        "2026-01-15",
        "2026-02-05",
        "2026-01-15",
        "2026-03-20",
        "2026-03-25",
        "2026-05-20",
        "2026-06-15",
    ],  # ID 1 e ID 4 têm erros cronológicos
    "idade": [
        35,
        40,
        35,
        20,
        25,
        45,
        50,
    ],  # ID 3 tem idade errada para o ano de 1995
    "Escolaridade":[12, 4, 4, 4, 15, 4, 6],
    "Renda": [
        8000,
        3000,
        8000,
        2800,
        np.nan,
        12000,
        np.nan,
    ],  # ID 4 e ID 6 têm Renda ausente
}

df = pd.DataFrame(dados)

print("=== DATAFRAME ORIGINAL (COM PROBLEMAS) ===")
print(df)
print("\n" + "=" * 80 + "\n")


# ==============================================================================
# 1. IDENTIFICAÇÃO DE DUPLICADOS (subset=['cpf', 'data_nascimento'], keep=False)
# ==============================================================================
# DIDÁTICA: O 'keep=False' marca TODAS as ocorrências do que está repetido como True.
# Isso nos permite isolar e visualizar o problema antes de tomar uma decisão.
print("--- 1. Identificando linhas duplicadas (CPF + Data Nasc) ---")
linhas_duplicadas = df[df.duplicated(subset=["cpf", "data_nascimento"], keep=False)]
print(linhas_duplicadas[["cpf", "data_nascimento", "cidade"]])

# Ação de Limpeza: Removemos as duplicadas mantendo apenas a primeira ocorrência
df = df.drop_duplicates(subset=["cpf", "data_nascimento"], keep="first")
print("\n" + "=" * 80 + "\n")


# ==============================================================================
# 2. PADRONIZAÇÃO DE TEXTO (str.upper() e str.strip())
# ==============================================================================
# DIDÁTICA: Evita que o computador trate " curitiba " ou "SÃO PAULO" como coisas
# diferentes de "CURITIBA" ou "São Paulo". Upper padroniza a caixa e Strip remove espaços nas pontas.
print("--- 2. Padronizando a coluna 'cidade' (Maiúsculas e sem espaços) ---")
df["cidade"] = df["cidade"].str.upper().str.strip()
print(df[["cidade"]])
print("\n" + "=" * 80 + "\n")


# ==============================================================================
# 3. MAPEAMENTO E TRATAMENTO DE CATEGORIAS (replace)
# ==============================================================================
# DIDÁTICA: Bases de dados reais costumam misturar siglas, inglês e português.
# Usamos um dicionário para converter todas as variações em um único termo padrão ("Brasil").
print("--- 3. Padronizando a coluna 'pais' via Dicionário de Mapeamento ---")
padroes = {"BR": "Brasil", "BRA": "Brasil", "Brazil": "Brasil"}
df["pais"] = df["pais"].replace(padroes)
print(df[["pais"]])
print("\n" + "=" * 80 + "\n")


# ==============================================================================
# 4. REGRA DE NEGÓCIO CRONOLÓGICA (data_fim >= data_inicio)
# ==============================================================================
# DIDÁTICA: Um processo não pode terminar antes de começar. Primeiro garantimos que
# o pandas entenda as colunas como datas reais e depois filtramos apenas as lógicas.
print("--- 4. Filtrando consistência cronológica (Data Fim >= Data Início) ---")
df["data_inicio"] = pd.to_datetime(df["data_inicio"])
df["data_fim"] = pd.to_datetime(df["data_fim"])

# Filtro: Mantém apenas onde o fim ocorre depois ou no mesmo dia do início
df = df[df["data_fim"] >= df["data_inicio"]]
print(df[["data_inicio", "data_fim"]])
print("\n" + "=" * 80 + "\n")


# ==============================================================================
# 5. VALIDAÇÃO CRUZADA DE DADOS (idade registrada == idade calculada)
# ==============================================================================
# DIDÁTICA: Verificamos se a idade escrita bate com o ano de nascimento baseado no
# ano de referência do projeto (2025). Se houver divergência (fraude ou erro), removemos.
print("--- 5. Validando Idade Registrada vs Idade Calculada (Ano Ref: 2025) ---")
df["idade_calc"] = 2025 - pd.to_datetime(df["data_nascimento"]).dt.year

# Filtro de consistência interna
df = df[df["idade"] == df["idade_calc"]]

# Removemos a coluna auxiliar para não poluir o DataFrame final
df = df.drop(columns=["idade_calc"])
print(df[["data_nascimento", "idade"]])
print("\n" + "=" * 80 + "\n")


# ==============================================================================
# 6. IMPUTAÇÃO PREDITIVA VIA REGRESSÃO LINEAR (Preenchimento inteligente de nulos)
# ==============================================================================
# DIDÁTICA: Para o ID 6, a renda está nula (NaN). Em vez de chutar a média, usamos a
# Idade e Escolaridade de quem já conhecemos para prever matematicamente a renda dele.
print("--- 6. Imputação Preditiva Pura por Regressão Linear para Renda ---")

# Separamos quem tem renda (treino) de quem não tem (imputação)
df_treino = df[df["Renda"].notna()]
df_imputar = df[df["Renda"].isna()]

X_treino = df_treino[["idade", "Escolaridade"]]
y_treino = df_treino["Renda"]
X_imputar = df_imputar[["idade", "Escolaridade"]]

# Treinamos a regressão linear simples
modelo = LinearRegression()
modelo.fit(X_treino, y_treino)

# Prevemos os valores exatos e aplicamos de volta nas linhas vazias
renda_prevista = modelo.predict(X_imputar)
df.loc[df["Renda"].isna(), "Renda"] = renda_prevista
print(df[["idade", "Escolaridade", "Renda"]])
print("\n" + "=" * 80 + "\n")


# ==============================================================================
# 7. NORMALIZAÇÃO DE ESCALA (MinMaxScaler)
# ==============================================================================
# DIDÁTICA: O último passo da esteira. Colocamos as colunas numéricas na mesma escala
# (entre 0 e 1) para que modelos de IA não priorizem a Renda só porque os números são maiores.
print("--- 7. Normalização Final com MinMaxScaler (Escala de 0 a 1) ---")
scaler = MinMaxScaler()
df[["idade", "renda_normalizada"]] = scaler.fit_transform(df[["idade", "Renda"]])

# Nota: Criei uma nova coluna 'renda_normalizada' apenas para manter a renda original legível na apresentação
print(df[["idade", "Renda", "renda_normalizada"]].round(2))
print("\n" + "=" * 80 + "\n")


# ==============================================================================
# RESULTADO FINAL DA ESTEIRA
# ==============================================================================
print("=== DATAFRAME FINAL COMPLETAMENTE TRATADO E PRONTO ===")
print(df.round(2))
