# -*- coding: utf-8 -*-
#"""Gestão de IMC.ipynb

#Automatically generated by Colab.

#Original file is located at
#    https://colab.research.google.com/drive/1OEH2FjwARRwrHXspIb6BVlfYK1FII2DY

# 12.03.25 - Este Script Serve para gerir os dados pessoais de uma pessoa
#- **Dados Pessoais (Nome, NIF, Idade, Peso, Altura, Gênero, IMC)**
#- **Calcula o Indice de massa corporal (IMC)**
#- **Alterar o Peso e Altura.**
#- **Grafico para mostrar o Histórico do IMC**
#- **Grafico para mostrar o Histórico atemporal do IMC**

# 23.04.25 - Fim

# Bibliotecas
#"""

import datetime
import matplotlib.pyplot as plt
import pandas as pd

#"""# Armazenar"""

# Lista para armazenar os dados das pessoas
pessoas = {}
# Lista para armazenar dados históricos (peso e altura ao longo do tempo)
historico_dados = []

#"""# Função Gerenciar Pessoas"""

def add_pessoa(nome, nif, idade, peso, altura, genero):
    """ Adiciona uma nova pessoa garantindo integridade dos dados """
    if nif in pessoas:
        print("Erro: Esse NIF já está cadastrado!")
        return
    if len(nif) > 15:
        print("Erro: O NIF deve ter no máximo 15 caracteres.")
        return
    if peso <= 0 or altura <= 0:
        print("Erro: Peso e altura devem ser valores positivos.")
        return

    pessoas[nif] = {
    "Nome": nome,
    "NIF": nif,
    "Idade": idade,
    "Peso": peso,
    "Altura": altura,
    "Gênero": genero,
    "IMC": calcular_imc(peso, altura),
    "CriadoEm": datetime.datetime.now()
    }
    print(f"✅ Pessoa adicionada com sucesso! Nome: {nome}, NIF: {nif}")

def calcular_imc(peso, altura):
    """ Calcula o IMC com base no peso e altura """
    return round(peso / (altura ** 2), 2)

def add_dados(nome, nif, peso, altura, data_hora=None):
    """Registra novo dado de peso/altura com validações de NIF, nome e data"""

    # 1. Verificar se o NIF existe
    if nif not in pessoas:
        print(f"❌ Erro: NIF '{nif}' não está cadastrado.")
        return

    # 2. Verificar se o nome bate com o nome cadastrado para o NIF
    nome_correto = pessoas[nif]["Nome"]
    if nome.strip().lower() != nome_correto.strip().lower():
        print(f"❌ Erro: O NIF '{nif}' pertence a '{nome_correto}', não a '{nome}'. Verifique os dados.")
        return

    # 3. Validar peso e altura
    if peso <= 0 or altura <= 0:
        print("❌ Erro: Peso e altura devem ser positivos.")
        return

    # 4. Interpretar a data informada (ou usar agora)
    if data_hora is None:
        data_obj = datetime.datetime.now()
        data_str = data_obj.strftime("%Y-%m-%d %H:%M:%S")
    else:
        try:
            data_obj = datetime.datetime.strptime(data_hora, "%Y-%m-%d %H:%M:%S")
            data_str = data_hora
        except ValueError:
            print("❌ Erro: Formato inválido. Use 'YYYY-MM-DD HH:MM:SS'.")
            return

    # 5. Verificar se a data é anterior à criação da pessoa
    data_criacao = pessoas[nif].get("CriadoEm")
    if data_criacao and data_obj < data_criacao:
        print(f"❌ Erro: A data fornecida ({data_str}) é anterior à criação da pessoa ({data_criacao.strftime('%Y-%m-%d %H:%M:%S')}).")
        return

    # 6. Registrar no histórico
    historico_dados.append({
        "Nome": nome,
        "NIF": nif,
        "Peso": peso,
        "Altura": altura,
        "IMC": calcular_imc(peso, altura),
        "Timestamp": data_str
    })
    print(f"📌 Dados atualizados para {nome} (NIF: {nif}) em {data_str}")

#"""# Funções para Listas"""

def listar_pessoas():
    """ Lista todas as pessoas cadastradas """
    if not pessoas:
        print("Nenhuma pessoa cadastrada.")
        return

    df = pd.DataFrame([{**{"NIF": nif}, **dados} for nif, dados in pessoas.items()])
    df = df[["Nome", "NIF", "Idade", "Peso", "Altura", "Gênero", "IMC"]]
    display(df)

def listar_por_imc_decrescente():
    """ Lista pessoas ordenadas pelo IMC de forma decrescente """
    df = pd.DataFrame([{**{"NIF": nif}, **dados} for nif, dados in pessoas.items()]).sort_values(by="IMC", ascending=False)
    df = df[["Nome", "NIF", "Peso", "Altura", "IMC"]]
    display(df)

def listar_pessoas_com_imc_critico():
    """ Lista pessoas com IMC fora da faixa saudável (IMC > 25 ou < 18.5) """
    df = pd.DataFrame([{**{"NIF": nif}, **dados} for nif, dados in pessoas.items()]).sort_values(by="IMC", ascending=False)
    df = df[["Nome", "NIF", "Peso", "Altura", "IMC"]]
    df_critico = df[(df["IMC"] > 25) | (df["IMC"] < 18.5)]
    display(df_critico)

#"""# Funções para Análise de Dados"""

def tabela_altura():
    """ Exibe uma tabela com a altura de todas as pessoas cadastradas """
    df = pd.DataFrame([{**{"NIF": nif}, **dados} for nif, dados in pessoas.items()])
    df = df[["Nome", "NIF", "Altura"]]
    display(df)

def tabela_peso():
    """ Exibe uma tabela com o peso de todas as pessoas cadastradas """
    df = pd.DataFrame([{**{"NIF": nif}, **dados} for nif, dados in pessoas.items()])
    df = df[["Nome", "NIF", "Peso"]]
    display(df)

def historico_imc(nif):
    """ Mostra gráfico de histórico de IMC com datas exatas inseridas """
    import matplotlib.dates as mdates

    df = pd.DataFrame(historico_dados)
    df_pessoa = df[df["NIF"] == nif].copy()

    if df_pessoa.empty:
        print(f"⚠️ Nenhum dado encontrado para o NIF '{nif}'.")
        return

    # Converter e ordenar
    df_pessoa["Timestamp"] = pd.to_datetime(df_pessoa["Timestamp"], format="%Y-%m-%d %H:%M:%S", errors="coerce")
    df_pessoa = df_pessoa.sort_values("Timestamp")

    nome = pessoas[nif]["Nome"]

    # Configurar figura
    plt.figure(figsize=(10, 5))
    plt.plot(df_pessoa["Timestamp"], df_pessoa["IMC"], marker='o', linestyle='-', color="#007acc", label="IMC")

    # Formatação eixos
    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%d-%m-%Y"))
    plt.gca().xaxis.set_major_locator(mdates.AutoDateLocator())

    # Estética
    plt.title(f"Histórico de IMC de {nome} (NIF: {nif})", fontsize=14, weight="bold")
    plt.xlabel("Data e Hora", fontsize=12)
    plt.ylabel("IMC", fontsize=12)
    plt.xticks(rotation=45)
    plt.grid(True, linestyle="--", alpha=0.5)
    plt.tight_layout()
    plt.legend()
    plt.show()

def historico_imc_atemporal(nif):
    """ Mostra gráfico de IMC por ordem de inserção (atemporal) """
    df = pd.DataFrame(historico_dados)
    df_pessoa = df[df["NIF"] == nif].copy()

    if df_pessoa.empty:
        print(f"⚠️ Nenhum dado encontrado para o NIF '{nif}'.")
        return

    nome = pessoas[nif]["Nome"]

    # Gerar eixo X baseado na ordem de inserção
    df_pessoa = df_pessoa.sort_values(by="Timestamp")  # opcional, se quiser manter ordem cronológica
    df_pessoa.reset_index(drop=True, inplace=True)
    df_pessoa["Ordem"] = df_pessoa.index + 1

    plt.figure(figsize=(10, 5))
    plt.plot(df_pessoa["Ordem"], df_pessoa["IMC"], marker='o', linestyle='-', color="#007acc", label="IMC")

    plt.title(f"Evolução de IMC (ordem de inserção) - {nome} (NIF: {nif})", fontsize=14, weight="bold")
    plt.xlabel("Medição", fontsize=12)
    plt.ylabel("IMC", fontsize=12)
    plt.grid(True, linestyle="--", alpha=0.5)
    plt.xticks(df_pessoa["Ordem"])
    plt.tight_layout()
    plt.legend()
    plt.show()

#"""# Integridade"""

def testar_integridade():
    """Testa integridade dos dados do sistema"""
    erros = []

    print("🔍 Verificando integridade de pessoas...")

    for nif, dados in pessoas.items():
        if len(nif) > 15:
            erros.append(f"❌ NIF '{nif}' tem mais de 15 caracteres.")

        campos_esperados = ["Nome", "Idade", "Peso", "Altura", "Gênero", "IMC"]
        for campo in campos_esperados:
            if campo not in dados:
                erros.append(f"❌ Campo '{campo}' ausente para NIF '{nif}'.")

        # Verificar valores
        peso = dados.get("Peso", 0)
        altura = dados.get("Altura", 0)
        imc_registrado = dados.get("IMC", 0)
        imc_calculado = calcular_imc(peso, altura)

        if not isinstance(peso, (int, float)) or peso <= 0:
            erros.append(f"❌ Peso inválido para NIF '{nif}'.")

        if not isinstance(altura, (int, float)) or altura <= 0:
            erros.append(f"❌ Altura inválida para NIF '{nif}'.")

        if abs(imc_registrado - imc_calculado) > 0.1:
            erros.append(f"⚠️ IMC divergente para NIF '{nif}': registrado={imc_registrado}, calculado={imc_calculado}")

    print("🔍 Verificando integridade do histórico de dados...")

    for idx, entrada in enumerate(historico_dados):
        nif = entrada.get("NIF")
        nome = entrada.get("Nome")
        peso = entrada.get("Peso")
        altura = entrada.get("Altura")
        imc = entrada.get("IMC")
        ts = entrada.get("Timestamp")

        if nif not in pessoas:
            erros.append(f"❌ Histórico #{idx}: NIF '{nif}' não cadastrado.")

        elif pessoas[nif]["Nome"] != nome:
            erros.append(f"❌ Histórico #{idx}: Nome '{nome}' não corresponde ao NIF '{nif}'.")

        if abs(calcular_imc(peso, altura) - imc) > 0.1:
            erros.append(f"⚠️ Histórico #{idx}: IMC inconsistente.")

        # Verifica timestamp
        try:
            datetime.datetime.strptime(ts, "%Y-%m-%d %H:%M:%S")
        except:
            erros.append(f"⚠️ Histórico #{idx}: Timestamp inválido '{ts}'.")

    if not erros:
        print("✅ Todos os dados estão consistentes.")
    else:
        print(f"⚠️ Foram encontrados {len(erros)} problemas:")
        for erro in erros:
            print(erro)

#"""# Lista de Pessoas"""

#listar_pessoas()

#"""# Adicionar Pessoa"""

# Adicionando pessoas
#add_pessoa(nome="Ana", nif="AB123456", idade=25, peso=70, altura=1.65, genero="Feminino")
#add_pessoa(nome="Carlos", nif="CD789012", idade=30, peso=85, altura=1.75, genero="Masculino")
#add_pessoa(nome="Rúben", nif="CD9023123", idade=20, peso=70, altura=1.70, genero="Masculino")

#"""# Adicionar Novos Dados
#- **Ao adicionar pessoas o código cria na data e hora que for adicionada**
#- **Não podes alterar o peso e altura anterior a criação da conta**
#"""

# Registrando dados ao longo do tempo
#add_dados(nome="Ana", nif="AB123456", peso=72, altura=1.65, data_hora="2025-04-28 08:00:00")
#add_dados(nome="Ana", nif="AB123456", peso=76, altura=1.65, data_hora="2025-04-30 11:00:00")
#add_dados(nome="Ana", nif="AB123456", peso=74, altura=1.65, data_hora="2025-08-06 15:30:00")

#"""# Listas"""

#listar_por_imc_decrescente()

#listar_pessoas_com_imc_critico()

#"""# Tabelas"""

#tabela_altura()

#tabela_peso()

#"""# Historico"""

#historico_imc(nif="AB123456")

#historico_imc_atemporal(nif="AB123456")

#"""Testar integridade"""

#testar_integridade()