import math
import os
import random
import string
import sys
import time

ALFABETO = list(string.ascii_uppercase)

# ==============================================================================
# 1. EXCEÇÕES E GERENCIAMENTO DE NAVEGAÇÃO GLOBAL
# ==============================================================================


class VoltarException(Exception):
    """Sinaliza o comando 'voltar' para retornar à tela/etapa anterior."""

    pass


class ReiniciarException(Exception):
    """Sinaliza o comando 'reiniciar' para voltar ao menu principal."""

    pass


def checar_comando_global(entrada):
    """Verifica se o usuário digitou 'sair', 'reiniciar' ou 'voltar'."""
    texto = str(entrada).lower().strip()
    if texto == "sair":
        print("\nDesligando o programa...")
        time.sleep(1)
        sys.exit()
    elif texto == "reiniciar":
        print("\nReiniciando a calculadora...")
        time.sleep(1)
        os.system("cls" if os.name == "nt" else "clear")
        raise ReiniciarException()
    elif texto == "voltar":
        raise VoltarException()


# ==============================================================================
# 2. FUNÇÕES DE ENTRADA, VALIDAÇÃO E FILTROS DE DADOS
# ==============================================================================


def pedir_inteiro(mensagem, min_val=None, max_val=None):
    """Valida entradas inteiras, limpa a tela e suporta comandos globais."""
    while True:
        entrada = input(mensagem).strip()
        checar_comando_global(entrada)

        if not entrada.lstrip("-").isdigit():
            print(
                f"\nA entrada '{entrada}' invalida! Digite um numero inteiro.\n"
            )
            time.sleep(1.5)
            os.system("cls" if os.name == "nt" else "clear")
            continue

        valor = int(entrada)
        if min_val is not None and max_val is not None:
            if valor < min_val or valor > max_val:
                print(
                    f"\nO numero {valor} e invalido! Escolha uma opcao entre {min_val} e {max_val}.\n"
                )
                time.sleep(1.5)
                os.system("cls" if os.name == "nt" else "clear")
                continue

        os.system("cls" if os.name == "nt" else "clear")
        return valor


def pedir_float(mensagem):
    """Valida números decimais, limpa a tela e suporta comandos globais."""
    while True:
        entrada = input(mensagem).replace(",", ".").strip()
        checar_comando_global(entrada)

        try:
            valor = float(entrada)
            os.system("cls" if os.name == "nt" else "clear")
            return valor
        except ValueError:
            print(
                f"\nA entrada '{entrada}' e invalida! Digite um numero valido.\n"
            )
            time.sleep(1.5)
            os.system("cls" if os.name == "nt" else "clear")


def pedir_texto(mensagem):
    """Lê uma string, limpa a tela e suporta comandos globais."""
    texto = input(mensagem)
    checar_comando_global(texto)
    os.system("cls" if os.name == "nt" else "clear")
    return texto


def pedir_sim_nao(mensagem):
    """Filtra respostas Sim/Não."""
    while True:
        resposta = input(mensagem).lower().strip()
        checar_comando_global(resposta)

        os.system("cls" if os.name == "nt" else "clear")
        if resposta in ["s", "sim"]:
            return True
        elif resposta in ["n", "nao", "não"]:
            return False
        else:
            print("Resposta invalida! Digite 's' ou 'n'.\n")
            time.sleep(1)
            os.system("cls" if os.name == "nt" else "clear")


def pedir_correcao(item_nome, valor_item):
    """Avaliação de critérios de correção: Certo, Meio Certo ou Errado."""
    valor_meio_certo = valor_item / 2.0

    while True:
        print("---------------------------------------------------")
        print(f" Corrigindo: {item_nome} (Valor: {valor_item:.2f} pts)")
        print(
            f" [c] Certo ({valor_item:.2f} pts) | [mc] Meio Certo ({valor_meio_certo:.2f} pts) | [e] Errado (0.00 pts)"
        )
        print("---------------------------------------------------")
        resposta = input("Qual o resultado do aluno?: ").lower().strip()
        checar_comando_global(resposta)

        os.system("cls" if os.name == "nt" else "clear")

        if resposta in ["c", "certo", "s", "sim"]:
            return valor_item
        elif resposta in ["mc", "meio", "meio certo"]:
            return valor_meio_certo
        elif resposta in ["e", "errado", "n", "nao", "não"]:
            return 0.0
        else:
            print(
                "Opcao invalida! Digite 'c' (certo), 'mc' (meio certo) ou 'e' (errado).\n"
            )
            time.sleep(1.5)
            os.system("cls" if os.name == "nt" else "clear")


def gerador_valores_aleatorios_prova(num_questoes, valor_total_simulado=10.0):
    """Sorteia valores para as questões garantindo que a soma feche no valor total estipulado."""
    if num_questoes <= 0:
        return []

    pesos = [random.uniform(1.0, 5.0) for _ in range(num_questoes)]
    soma_pesos = sum(pesos)

    valores = [
        round((p / soma_pesos) * valor_total_simulado, 2) for p in pesos
    ]

    soma_atual = sum(valores)
    diferenca = round(valor_total_simulado - soma_atual, 2)

    if diferenca != 0:
        valores[-1] = round(valores[-1] + diferenca, 2)

    return valores


def pedir_alternativa_valida(mensagem, opcoes_letras):
    """Valida a alternativa correta escolhida."""
    while True:
        resp = input(mensagem).upper().strip()
        checar_comando_global(resp)

        if resp in opcoes_letras:
            os.system("cls" if os.name == "nt" else "clear")
            return resp
        else:
            print(
                f"Opcao invalida! Escolha entre: {', '.join(opcoes_letras)}\n"
            )
            time.sleep(1.5)
            os.system("cls" if os.name == "nt" else "clear")


def exibir_manual():
    """Painel descritivo de comandos globais."""
    print("===================================================")
    print("      MANUAL MAKER - GUIA DE ABREVIACOES           ")
    print("===================================================")
    print(" Comandos Globais (Funcionam em qualquer tela):")
    print("   [sair]      -> Fecha o programa imediatamente")
    print("   [reiniciar] -> Voltar ao Menu Principal")
    print("   [voltar]    -> Volta para a tela/etapa anterior")
    print("===================================================\n")


# ==============================================================================
# 3. IMPLEMENTAÇÃO DAS 10 OPÇÕES DO MENU
# ==============================================================================


def opcao_1_calcular_nota_aluno():
    print("=== 1. CALCULADOR DE NOTA INDIVIDUAL DE ALUNO ===")
    nome = pedir_texto("Nome do aluno: ")
    qtd_questoes = pedir_inteiro("Quantidade de questoes da prova: ")
    valor_prova = pedir_float("Valor total da prova: ")

    valor_por_questao = valor_prova / qtd_questoes
    nota_final = 0.0

    for i in range(1, qtd_questoes + 1):
        pontos = pedir_correcao(f"Questao {i}", valor_por_questao)
        nota_final += pontos

    print("===================================================")
    print(f" Aluno: {nome}")
    print(f" Nota Final: {nota_final:.2f} / {valor_prova:.2f}")
    print("===================================================")
    input("\nPressione ENTER para voltar ao menu...")
    os.system("cls" if os.name == "nt" else "clear")


def opcao_2_porcentagem_nota():
    print("=== 2. CALCULADORA DE PORCENTAGEM DE NOTA ===")
    nota_obtida = pedir_float("Nota obtida pelo aluno: ")
    nota_maxima = pedir_float("Nota maxima da prova: ")

    if nota_maxima == 0:
        print("A nota maxima nao pode ser zero!")
    else:
        porcentagem = (nota_obtida / nota_maxima) * 100
        print("===================================================")
        print(f" Rendimento do aluno: {porcentagem:.2f}%")
        print("===================================================")

    input("\nPressione ENTER para voltar ao menu...")
    os.system("cls" if os.name == "nt" else "clear")


def opcao_3_boletim_aluno():
    print("=== 3. GERADOR DE BOLETIM DE UM ALUNO ===")
    nome = pedir_texto("Nome do aluno: ")
    qtd_materias = pedir_inteiro("Quantidade de materias: ")

    boletim = []
    soma_notas = 0.0

    for i in range(1, qtd_materias + 1):
        materia = pedir_texto(f"Nome da materia {i}: ")
        nota = pedir_float(f"Nota em {materia}: ")
        boletim.append((materia, nota))
        soma_notas += nota

    media = soma_notas / qtd_materias if qtd_materias > 0 else 0

    print("===================================================")
    print(f" BOLETIM ESCOLAR - ALUNO: {nome}")
    print("---------------------------------------------------")
    for mat, n in boletim:
        status = "Aprovado" if n >= 6.0 else "Recuperacao/Reprovado"
        print(f" {mat:<20} | Nota: {n:>5.2f} | Status: {status}")
    print("---------------------------------------------------")
    print(f" MEDIA GERAL: {media:.2f}")
    print("===================================================")
    input("\nPressione ENTER para voltar ao menu...")
    os.system("cls" if os.name == "nt" else "clear")


def opcao_4_sair():
    print("Desligando o programa...")
    time.sleep(1)
    sys.exit()


def opcao_5_reiniciar():
    print("Reiniciando a calculadora...")
    time.sleep(1)
    os.system("cls" if os.name == "nt" else "clear")
    raise ReiniciarException()


def opcao_6_calculadora_integradat():
    print("=== 6. CALCULADORA MULTIFUNCAO ===")
    print("1. Soma (+)")
    print("2. Subtracao (-)")
    print("3. Multiplicacao (*)")
    print("4. Divisao (/)")
    print("5. Raiz Quadrada (v)")
    print("6. Potenciacao (^)")

    op = pedir_inteiro("Escolha a operacao (1-6): ", min_val=1, max_val=6)

    if op in [1, 2, 3, 4, 6]:
        num1 = pedir_float("Digite o primeiro numero: ")
        num2 = pedir_float("Digite o segundo numero: ")

        if op == 1:
            res = num1 + num2
        elif op == 2:
            res = num1 - num2
        elif op == 3:
            res = num1 * num2
        elif op == 4:
            if num2 == 0:
                print("\nErro: Divisao por zero!")
                input("\nPressione ENTER para continuar...")
                os.system("cls" if os.name == "nt" else "clear")
                return
            res = num1 / num2
        elif op == 6:
            res = num1**num2

        print(f"\nResultado: {res:.4f}")

    elif op == 5:
        num = pedir_float("Digite o numero para extrair a raiz quadrada: ")
        if num < 0:
            print("\nErro: Nao existe raiz de numero negativo nos reais!")
        else:
            print(f"\nResultado: {math.sqrt(num):.4f}")

    input("\nPressione ENTER para voltar ao menu...")
    os.system("cls" if os.name == "nt" else "clear")


def opcao_7_boletim_multialuno():
    print("=== 7. BOLETIM DE MULTIPLOS ALUNOS ===")
    qtd_alunos = pedir_inteiro("Quantidade de alunos: ")
    qtd_materias = pedir_inteiro("Quantidade de materias por aluno: ")

    materias = []
    for m in range(1, qtd_materias + 1):
        materias.append(pedir_texto(f"Nome da materia {m}: "))

    turma = []
    for a in range(1, qtd_alunos + 1):
        nome_aluno = pedir_texto(f"Nome do aluno {a}: ")
        notas_aluno = {}
        soma = 0.0

        for mat in materias:
            n = pedir_float(f"Nota de {nome_aluno} em {mat}: ")
            notas_aluno[mat] = n
            soma += n

        media = soma / qtd_materias if qtd_materias > 0 else 0
        turma.append(
            {"nome": nome_aluno, "notas": notas_aluno, "media": media}
        )

    print("===================================================================")
    print("                    RESUMO DA TURMA - BOLETIM                      ")
    print("===================================================================")
    cabecalho = f"{'ALUNO':<15} | " + " | ".join(
        [f"{m[:8]:<8}" for m in materias]
    ) + " | MEDIA"
    print(cabecalho)
    print("-" * len(cabecalho))

    for al in turma:
        linha_notas = " | ".join(
            [f"{al['notas'][m]:>8.2f}" for m in materias]
        )
        print(f"{al['nome']:<15} | {linha_notas} | {al['media']:>5.2f}")

    print("===================================================================")
    input("\nPressione ENTER para voltar ao menu...")
    os.system("cls" if os.name == "nt" else "clear")


def opcao_8_provas_multimaterias():
    print("=== 8. GERADOR DE PROVA MULTIMATERIAS (SOMA = 10.0) ===")
    qtd_questoes = pedir_inteiro("Quantidade de questoes da prova: ")
    valor_total = 10.0
    valores_q = gerador_valores_aleatorios_prova(qtd_questoes, valor_total)

    questoes = []
    for i in range(qtd_questoes):
        num_q = i + 1
        mat = pedir_texto(f"Materia da questao {num_q}: ")
        perg = pedir_texto(f"Enunciado da questao {num_q}: ")
        questoes.append(
            {"num": num_q, "materia": mat, "perg": perg, "valor": valores_q[i]}
        )

    print("=================================================================")
    print("                  TABELA DA PROVA MULTIMATERIAS                  ")
    print("=================================================================")
    print(f"{'QUESTÃO':<10} | {'MATERIA':<15} | {'ENUNCIADO':<30} | VALOR")
    print("-----------------------------------------------------------------")
    for q in questoes:
        p_corta = q["perg"][:27] + "..." if len(q["perg"]) > 30 else q["perg"]
        print(
            f"{q['num']:<10} | {q['materia']:<15} | {p_corta:<30} | {q['valor']:.2f} pts"
        )
    print("=================================================================")
    input("\nPressione ENTER para voltar ao menu...")
    os.system("cls" if os.name == "nt" else "clear")


def opcao_9_prova_simples():
    print("=== 9. GERADOR DE PROVA SIMPLES (SOMA = 10.0) ===")
    qtd_questoes = pedir_inteiro("Quantidade de questoes: ")
    valor_total = 10.0
    valores_q = gerador_valores_aleatorios_prova(qtd_questoes, valor_total)

    questoes = []
    for i in range(qtd_questoes):
        num_q = i + 1
        perg = pedir_texto(f"Pergunta da questao {num_q}: ")
        questoes.append({"num": num_q, "perg": perg, "valor": valores_q[i]})

    print("=========================================================")
    print("               TABELA DE PROVA SIMPLES                   ")
    print("=========================================================")
    print(f"{'QUESTÃO':<10} | {'PERGUNTA':<35} | VALOR")
    print("---------------------------------------------------------")
    for q in questoes:
        p_corta = q["perg"][:32] + "..." if len(q["perg"]) > 35 else q["perg"]
        print(f"{q['num']:<10} | {p_corta:<35} | {q['valor']:.2f} pts")
    print("=========================================================")
    input("\nPressione ENTER para voltar ao menu...")
    os.system("cls" if os.name == "nt" else "clear")


def opcao_10_gerar_simulado_completo():
    """Gera o simulado completo com valores sorteados por questão, prova pronta e gabarito."""
    print("=== 10. GERADOR DE SIMULADO COMPLETO ===")
    time.sleep(1)
    os.system("cls" if os.name == "nt" else "clear")

    valor_total_simulado = pedir_float(
        "Quanto vale o simulado inteiro (valor total): "
    )
    qtd_questoes = pedir_inteiro("Quantas questoes tem o simulado: ")
    qtd_alternativas = pedir_inteiro(
        "Quantas alternativas por questao (ex: 4 para A,B,C,D / 5 para A,B,C,D,E): ",
        min_val=2,
        max_val=10,
    )

    opcoes_letras = ALFABETO[:qtd_alternativas]

    # Sorteia valores aleatórios por questão que fecham exatamente no valor total do simulado
    valores_questoes = gerador_valores_aleatorios_prova(
        qtd_questoes, valor_total_simulado
    )
    dados_simulado = []

    q = 0
    while q < qtd_questoes:
        num_q = q + 1

        try:
            item_atual = (
                dados_simulado[q]
                if q < len(dados_simulado)
                else {
                    "num_q": num_q,
                    "materia": "",
                    "pergunta": "",
                    "alternativas": {},
                    "valor_q": valores_questoes[q],
                    "valor_alt": round(
                        valores_questoes[q] / qtd_alternativas, 2
                    ),
                    "resposta": "",
                }
            )

            if not item_atual["materia"]:
                item_atual["materia"] = pedir_texto(
                    f"Qual a materia da questao {num_q}: "
                )

            if not item_atual["pergunta"]:
                item_atual["pergunta"] = pedir_texto(
                    f"Qual a pergunta da questao {num_q}: "
                )

            # Preenchimento das Alternativas (com painel de estado no topo)
            idx_alt = len(item_atual["alternativas"])
            while idx_alt < len(opcoes_letras):
                letra = opcoes_letras[idx_alt]
                os.system("cls" if os.name == "nt" else "clear")

                print("=========================================================")
                print(
                    f" QUESTAO {num_q} [{item_atual['materia']}] - Valor Sorteado: {item_atual['valor_q']:.2f} pts"
                )
                print(f" Pergunta: {item_atual['pergunta']}")
                print("---------------------------------------------------------")
                if item_atual["alternativas"]:
                    print(" Alternativas ja cadastradas:")
                    for l_cad, txt_cad in item_atual["alternativas"].items():
                        print(f"   ({l_cad}) {txt_cad}")
                    print(
                        "---------------------------------------------------------"
                    )
                print(
                    "=========================================================\n"
                )

                try:
                    texto_alt = pedir_texto(
                        f"Digite a alternativa {letra} da questao {num_q}: "
                    )
                    item_atual["alternativas"][letra] = texto_alt
                    idx_alt += 1
                except VoltarException:
                    if idx_alt > 0:
                        letra_anterior = opcoes_letras[idx_alt - 1]
                        del item_atual["alternativas"][letra_anterior]
                        idx_alt -= 1
                    else:
                        item_atual["pergunta"] = ""
                        raise VoltarException()

            # Escolha da resposta correta
            os.system("cls" if os.name == "nt" else "clear")
            print("=========================================================")
            print(
                f" QUESTAO {num_q} [{item_atual['materia']}] - Valor Sorteado: {item_atual['valor_q']:.2f} pts"
            )
            print(f" Pergunta: {item_atual['pergunta']}")
            print("---------------------------------------------------------")
            print(" Alternativas cadastradas:")
            for l_cad, txt_cad in item_atual["alternativas"].items():
                print(f"   ({l_cad}) {txt_cad}")
            print("=========================================================\n")

            letras_str = ", ".join(opcoes_letras)
            try:
                item_atual["resposta"] = pedir_alternativa_valida(
                    f"Qual a resposta CORRETA da questao {num_q}? ({letras_str}): ",
                    opcoes_letras,
                )
            except VoltarException:
                letra_ultima = opcoes_letras[-1]
                del item_atual["alternativas"][letra_ultima]
                continue

            if q < len(dados_simulado):
                dados_simulado[q] = item_atual
            else:
                dados_simulado.append(item_atual)

            q += 1

        except VoltarException:
            if item_atual["pergunta"] == "":
                item_atual["materia"] = ""
            elif item_atual["materia"] == "":
                if q > 0:
                    q -= 1
                else:
                    raise VoltarException()

    # --- 1. TABELA COMPLETA COM VALORES SORTEADOS ---
    print(
        "=========================================================================================="
    )
    print(
        "                               TABELA COMPLETA DO SIMULADO                                "
    )
    print(
        "=========================================================================================="
    )
    print(
        f"{'Nº QUESTÃO':<12} | {'MATERIA':<18} | {'PERGUNTA':<35} | {'VALOR Q.':<10} | {'VALOR ALT.':<10}"
    )
    print(
        "------------------------------------------------------------------------------------------"
    )

    for item in dados_simulado:
        p_corta = (
            item["pergunta"][:32] + "..."
            if len(item["pergunta"]) > 35
            else item["pergunta"]
        )
        print(
            f"{item['num_q']:<12} | {item['materia']:<18} | {p_corta:<35} | {item['valor_q']:<10.2f} | {item['valor_alt']:<10.2f}"
        )

    print(
        "------------------------------------------------------------------------------------------"
    )
    print(
        f" VALOR TOTAL DO SIMULADO: {sum(item['valor_q'] for item in dados_simulado):.2f} pts"
    )
    print(
        "=========================================================================================="
    )
    print("\n")

    # --- 2. EXIBIÇÃO DA PROVA PRONTA ---
    print(
        "=========================================================================================="
    )
    print(
        "                                 PROVA DO SIMULADO                                       "
    )
    print(
        "=========================================================================================="
    )

    for item in dados_simulado:
        print(
            f"Questao {item['num_q']} [{item['materia']}] - ({item['valor_q']:.2f} pts)"
        )
        print(f"Pergunta: {item['pergunta']}")
        print("Alternativas:")

        for letra in opcoes_letras:
            conteudo = item["alternativas"][letra]
            print(f"  ({letra}) {conteudo}")

        print(
            "\n------------------------------------------------------------------------------------------"
        )

    print("\n")

    # --- 3. EXIBIÇÃO DO GABARITO OFICIAL ---
    print(
        "=========================================================================="
    )
    print(
        "                            GABARITO OFICIAL                              "
    )
    print(
        "=========================================================================="
    )

    cabecalho_letras = " | ".join([f"  {letra}  " for letra in opcoes_letras])
    print(f"{'QUESTÃO':<10} | {cabecalho_letras}")
    print(
        "--------------------------------------------------------------------------"
    )

    for item in dados_simulado:
        linha_opcoes = []
        for letra in opcoes_letras:
            if letra == item["resposta"]:
                linha_opcoes.append(" [X] ")
            else:
                linha_opcoes.append(" ( ) ")

        formato_linha = " | ".join(linha_opcoes)
        print(f"  {item['num_q']:<8} | {formato_linha}")

    print(
        "=========================================================================="
    )

    input("\nPressione ENTER para voltar ao menu principal...")
    os.system("cls" if os.name == "nt" else "clear")


# ==============================================================================
# 4. ESTRUTURA E LOOP PRINCIPAL DO PROGRAMA
# ==============================================================================

while True:
    try:
        exibir_manual()

        print("+++++++++++++++++++++++++++++++++++++++++++++++++++")
        print("+     BEM VINDO AO CALCULADOR DE NOTAS            +")
        print("+   ALEM DE NOTAS VEM UMA CALCULADORA INTEGRADA   +")
        print("+       E E TOTALMENTE EM PYTHON E GRATUITO       +")
        print("+++++++++++++++++++++++++++++++++++++++++++++++++++")
        time.sleep(1)

        definicao = pedir_inteiro(
            "digite 1 para calcular a nota do aluno\n"
            "digite 2 para fazer a porcentagem da nota do aluno\n"
            "digite 3 para fazer um boletim\n"
            "digite 4 para sair\n"
            "digite 5 para reiniciar\n"
            "digite 6 para calcular numeros\n"
            "digite 7 para boletim multialuno\n"
            "digite 8 para gerar provas multimaterias com tabela (Soma = 10.0)\n"
            "digite 9 para gerar prova simples (sem materia) com tabela (Soma = 10.0)\n"
            "digite 10 para GERAR SIMULADO (Tabela + Prova Completa + Gabarito Personalizado)\n"
            "escolha uma opcao (1-10): ",
            min_val=1,
            max_val=10,
        )

        if definicao == 1:
            opcao_1_calcular_nota_aluno()
        elif definicao == 2:
            opcao_2_porcentagem_nota()
        elif definicao == 3:
            opcao_3_boletim_aluno()
        elif definicao == 4:
            opcao_4_sair()
        elif definicao == 5:
            opcao_5_reiniciar()
        elif definicao == 6:
            opcao_6_calculadora_integradat()
        elif definicao == 7:
            opcao_7_boletim_multialuno()
        elif definicao == 8:
            opcao_8_provas_multimaterias()
        elif definicao == 9:
            opcao_9_prova_simples()
        elif definicao == 10:
            opcao_10_gerar_simulado_completo()

    except VoltarException:
        print("\nVoltando para a tela anterior...")
        time.sleep(1)
        os.system("cls" if os.name == "nt" else "clear")
        continue
    except ReiniciarException:
        continue
    except Exception as e:
        print(f"\nOcorreu um erro inesperado: {e}")
        time.sleep(2)
        os.system("cls" if os.name == "nt" else "clear")