import json
import mimetypes
from pathlib import Path
import re
import unicodedata

import requests

from app.gerador_csv import TOTAL_PERGUNTAS, carregar_leituras, nome_arquivo_keepedu, resposta
from app.services import limpar_pos_importacao_keepedu
from app.services import (
    caminho_leituras,
    localizar_imagem_original,
)
from app.settings import (
    ID_PROVA_KEEPEDU,
    KEEPEDU_API_KEY,
    KEEPEDU_IMPORTAR_FOLHA_RESPOSTA_URL,
    KEEPEDU_IMPORTAR_LISTA_FREQUENCIA_URL,
    KEEPEDU_IMPORTAR_RESPOSTAS_URL,
    KEEPEDU_IMPORTAR_TIMEOUT_SECONDS,
    KEEPEDU_SIMULAR_IMPORTAR_RESPOSTAS_URL,
    KEEPEDU_INSTITUTE,
)
from app.validacao_cadastral import (
    carregar_validacao_manual,
    gerar_validacao_cadastral,
    obter_validacao_cadastral_para_envio,
    resolver_validacao_cartao,
    limpar_pendencia_importacao,
    registrar_envio_keepedu,
    registrar_pendencia_importacao,
)

print("\n=========== SETTINGS KEEPEDU ===========")
print("KEEPEDU_IMPORTAR_RESPOSTAS_URL =", KEEPEDU_IMPORTAR_RESPOSTAS_URL)
print("KEEPEDU_IMPORTAR_FOLHA_RESPOSTA_URL =", KEEPEDU_IMPORTAR_FOLHA_RESPOSTA_URL)
print("KEEPEDU_IMPORTAR_LISTA_FREQUENCIA_URL =", KEEPEDU_IMPORTAR_LISTA_FREQUENCIA_URL)
print("========================================\n")

def caminho_relatorio_importacao(nome_processamento: str, modo: str = "importacao") -> Path:
    caminho_leitura = caminho_leituras(nome_processamento)
    pasta_processamento = caminho_leitura.parent
    nome_arquivo = "simulacao_keepedu.json" if modo == "simulacao" else "importacao_keepedu.json"
    return pasta_processamento / nome_arquivo


def _texto_limpo(valor) -> str:
    if valor is None:
        return ""
    return str(valor).strip()


def _montar_respostas_objetivas(dados_cartao: dict) -> list[str]:
    respostas = dados_cartao.get("respostas", {})
    return [resposta(respostas, numero) for numero in range(9, TOTAL_PERGUNTAS + 1)]


def _detectar_id_aval(dados_validacao: dict, id_aval_informado) -> str:
    id_aval = _texto_limpo(id_aval_informado)
    if id_aval:
        return id_aval
    resumo = dados_validacao.get("resumo", {})
    id_aval = _texto_limpo(resumo.get("id_prova_processamento"))
    if id_aval:
        return id_aval
    return _texto_limpo(ID_PROVA_KEEPEDU)


def _cabecalhos_keepedu() -> dict:
    return {
        "ApiKey": KEEPEDU_API_KEY,
        "Institute": KEEPEDU_INSTITUTE,
        "Content-Type": "application/json",
        "Accept": "application/json",
    }


def _cabecalhos_keepedu_upload() -> dict:
    return {
        "ApiKey": KEEPEDU_API_KEY,
        "Institute": KEEPEDU_INSTITUTE,
        "Accept": "application/json",
    }


def _payload_base(id_aval: str) -> dict:
    id_aval_payload = int(id_aval) if id_aval.isdigit() else id_aval
    return {"idAval": id_aval_payload}


def _montar_aluno_payload(
    nome_imagem: str,
    dados_cartao: dict,
    validacao: dict,
    id_aval: str,
    nome_arquivo: str,
    modelo_ia: str | None = None,
) -> dict:
    barcode = _texto_limpo(validacao.get("codigo_barras_final"))
    id_aluno = _texto_limpo(validacao.get("id_final"))

    if barcode and "A" in barcode:
        id_aval_barcode = barcode.split("A", 1)[0].strip()
        if id_aval and id_aval_barcode and id_aval_barcode != id_aval:
            raise ValueError(
                f"O barcode do cartão {nome_imagem} usa idAval {id_aval_barcode}, "
                f"mas o envio foi configurado com {id_aval}."
            )

    if not barcode and not id_aluno:
        raise ValueError(f"O cartão {nome_imagem} não possui barcode final nem idAluno final.")

    print("\n===== NOME ARQUIVO FINAL =====")
    print(nome_arquivo)
    print("=============================\n")
    
    
    respostas = dados_cartao.get("respostas", {})
    aluno = {
        "nomeArquivo": nome_arquivo,  # <--- Mude aqui para usar a variável corrigida!
        "lingua": resposta(respostas, 7),
        "respostas": _montar_respostas_objetivas(dados_cartao),
        "modelo_ia": modelo_ia or "KEEPEDU_AI",
    }

    if barcode:
        aluno["barcode"] = barcode
    else:
        aluno["idAluno"] = id_aluno

    return aluno


def _salvar_relatorio(nome_processamento: str, relatorio: dict, modo: str) -> str:
    caminho_saida = caminho_relatorio_importacao(nome_processamento, modo=modo)
    with open(caminho_saida, "w", encoding="utf-8") as arquivo:
        json.dump(relatorio, arquivo, ensure_ascii=False, indent=4)
    return str(caminho_saida)


def _normalizar_texto_busca(valor) -> str:
    texto = unicodedata.normalize("NFKD", str(valor or ""))
    texto = texto.encode("ascii", "ignore").decode("ascii")
    return " ".join(texto.lower().split())


def _formatar_stem_arquivo_keepedu(nome_original_real: str) -> str:
    """Nome base KeepEdu: stem do arquivo com hífens (ex.: 12763-ls-adaptad...-002)."""
    return nome_arquivo_keepedu(nome_original_real).replace("_", "-").lower()


def _formatar_nome_arquivo_imagem_keepedu(nome_original_real: str) -> str:
    nome = _formatar_stem_arquivo_keepedu(nome_original_real)
    if nome.endswith(".jpeg"):
        nome = re.sub(r"\.jpeg$", ".jpg", nome, flags=re.IGNORECASE)
    elif not nome.endswith(".jpg"):
        nome += ".jpg"
    return nome


def _resposta_indica_problema_folha_resposta(corpo_resposta: dict) -> bool:
    texto_resposta = _normalizar_texto_busca(json.dumps(corpo_resposta or {}, ensure_ascii=False))
    padroes = (
        "folha_de_resposta",
        "importe primeiro as respostas presenciais",
        "arquivo nao importado",
        "nome do arquivo deve coincidir",
    )
    return any(padrao in texto_resposta for padrao in padroes)


def _resposta_indica_aluno_fora_avaliacao(corpo_resposta: dict) -> bool:
    if _resposta_indica_problema_folha_resposta(corpo_resposta):
        return False
    texto_resposta = _normalizar_texto_busca(json.dumps(corpo_resposta or {}, ensure_ascii=False))
    padroes = (
        "nao pertence a avaliacao",
        "nao pertence a esta avaliacao",
        "nao esta vinculado a avaliacao",
        "nao esta vinculado nesta avaliacao",
        "nao esta incluido na avaliacao",
        "nao esta incluso na avaliacao",
        "nao participa da avaliacao",
        "aluno nao pertence a avaliacao",
        "aluno nao esta na avaliacao",
        "aluno nao cadastrado na avaliacao",
        "aluno nao localizado na avaliacao",
        "nao encontrado na avaliacao",
    )
    return any(padrao in texto_resposta for padrao in padroes)


def _mensagens_resposta(corpo_resposta: dict) -> list[str]:
    mensagens = []
    for chave in ("mensagem", "message", "error", "erro", "arquivoErros"):
        valor = (corpo_resposta or {}).get(chave)
        if isinstance(valor, list):
            mensagens.extend(str(item).strip() for item in valor if str(item).strip())
        elif valor:
            mensagens.append(str(valor).strip())
    return mensagens


def _resumir_motivo_falha_importacao(
    status_code: int | None = None,
    corpo_resposta: dict | None = None,
    exc: BaseException | None = None,
) -> str:
    if exc is not None:
        texto = str(exc).strip()
        return texto or "Erro inesperado durante o processamento."
    mensagens = _mensagens_resposta(corpo_resposta or {})
    if mensagens:
        return " | ".join(mensagens[:3])
    if status_code is not None and not (200 <= status_code < 300):
        return f"A API retornou HTTP {status_code}."
    if isinstance(corpo_resposta, dict) and corpo_resposta.get("success") is False:
        return "A API recusou o envio sem informar o motivo."
    return ""


def _descricao_resultado_item(
    nome_item: str,
    sucesso: bool,
    motivo_falha: str = "",
    *,
    texto_sucesso: str,
    prefixo_falha: str,
) -> str:
    if sucesso:
        return texto_sucesso.format(nome=nome_item)
    motivo = str(motivo_falha or "").strip()
    if motivo:
        return f"{prefixo_falha} {nome_item}: {motivo}"
    return f"{prefixo_falha} {nome_item}."


def _resumir_resposta_http_nao_json(status_code: int, resposta_api: requests.Response, contexto: str) -> str:
    texto = str(resposta_api.text or "").strip()
    texto_compacto = " ".join(texto.split())

    if not texto_compacto:
        return f"{contexto}: HTTP {status_code} sem corpo JSON."
    if "<html" in texto_compacto.lower() or "<!doctype" in texto_compacto.lower():
        return f"{contexto}: HTTP {status_code} retornou HTML em vez de JSON."

    texto_limpo = re.sub(r"<[^>]+>", " ", texto_compacto)
    texto_limpo = " ".join(texto_limpo.split())

    if not texto_limpo:
        return f"{contexto}: HTTP {status_code} sem JSON válido."
    if len(texto_limpo) > 220:
        return f"{texto_limpo[:217]}..."

    return f"{contexto}: HTTP {status_code} - {texto_limpo}"


def _post_keepedu(url_destino: str, payload_envio: dict, cabecalhos: dict) -> tuple[int, dict]:
    resposta_api = requests.post(
        url_destino,
        json=payload_envio,
        headers=cabecalhos,
        timeout=KEEPEDU_IMPORTAR_TIMEOUT_SECONDS,
    )
    
    print("\n========== ENVIO VETOR KEEPEDU ==========")
    print("URL:", url_destino)

    print("\nPAYLOAD:")
    print(json.dumps(payload_envio, indent=4, ensure_ascii=False))

    print("\nHEADERS:")
    print(cabecalhos)

    print("=========================================\n")
    
    status_code = resposta_api.status_code
    try:
        corpo_resposta = resposta_api.json()
    except ValueError:
        corpo_resposta = {
            "success": False,
            "mensagem": [_resumir_resposta_http_nao_json(status_code, resposta_api, "Importação de respostas")],
        }
        
    print("\n========== RESPOSTA KEEPEDU ==========")
    print("STATUS:", status_code)
    print("BODY:", resposta_api.text)
    print("======================================\n")
    return status_code, corpo_resposta

    # print("\n===== IMAGEM =====")
    # print("nomeArquivo =", nome_arquivo_formatado)
    # print("==================\n")
def _post_keepedu_folha_resposta(
    url_destino: str,
    caminho_imagem: Path,
    id_aval,
    cabecalhos: dict,
    nome_arquivo_limpo: str = None  # <--- ADICIONE ESTE PARÂMETRO
) -> tuple[int, dict]:
    content_type = mimetypes.guess_type(caminho_imagem.name)[0] or "application/octet-stream"
    # Se não passarmos o nome limpo, usa o do arquivo físico
    nome_final_envio = nome_arquivo_limpo or caminho_imagem.name 

    with open(caminho_imagem, "rb") as arquivo_imagem:
        resposta_api = requests.post(
            url_destino,
            data={"idAval": str(id_aval)},
            files={
                "imagem_folha_resposta": (
                    nome_final_envio,  # <--- USA O NOME LIMPO SEM 'template_'
                    arquivo_imagem,
                    content_type,
                )
            },
            headers=cabecalhos,
            timeout=KEEPEDU_IMPORTAR_TIMEOUT_SECONDS,
        )
        
        print("\n========== KEEPEDU UPLOAD ==========")
        print("URL:", url_destino)
        print("ID AVAL:", id_aval)
        print("ARQUIVO:", nome_final_envio)
        print("CAMINHO:", caminho_imagem)
        print("====================================\n")
        
        print("STATUS:", resposta_api.status_code)
        print("BODY:", resposta_api.text)
        
        print("\n===== DEBUG ENVIO IMAGEM =====")
        print("nome_final_envio =", nome_final_envio)
        print("caminho_imagem.name =", caminho_imagem.name)
        print("=============================\n")
        
        print("\n===== MULTIPART =====")
        print("field =", "imagem_folha_resposta")
        print("filename =", nome_final_envio)
        print("content_type =", content_type)
        print("=====================\n")
        
    # ... resto da função permanece igual
    status_code = resposta_api.status_code
    try:
        corpo_resposta = resposta_api.json()
    except ValueError:
        corpo_resposta = {
            "success": 200 <= status_code < 300,
            "mensagem": [_resumir_resposta_http_nao_json(status_code, resposta_api, "Upload da folha-resposta")],
        }
    return status_code, corpo_resposta


def _montar_url_folha_resposta(url_base: str, id_aval) -> str:
    url = str(url_base or "").strip()
    id_aval_texto = str(id_aval).strip()
    if not url or not id_aval_texto:
        return url
    return (
        url.replace("{idAval}", id_aval_texto)
        .replace("{id_aval}", id_aval_texto)
        .replace(":idAval", id_aval_texto)
        .replace(":id_digest", id_aval_texto)
    )


def _sucesso_resposta_generica(status_code: int, corpo_resposta: dict) -> bool:
    if not (200 <= status_code < 300):
        return False
    if isinstance(corpo_resposta, dict) and "success" in corpo_resposta:
        return bool(corpo_resposta.get("success"))
    return True


def _motivo_pendencia_avaliacao(corpo_resposta: dict, nome_arquivo: str) -> str:
    mensagens = _mensagens_resposta(corpo_resposta)
    if mensagens:
        return " | ".join(mensagens[:3])
    return f"O aluno do cartão {nome_arquivo} não pertence à avaliação informada."


def _emitir_progresso_importacao(
    progresso_callback,
    percentual: int,
    mensagem: str,
    arquivo_atual: str = "",
    resultado: dict | None = None,
    tipo: str | None = None,
    titulo: str | None = None,
    descricao: str = "",
    atualizar_evento: bool = False,
):
    if not progresso_callback:
        return
    progresso_callback(
        percentual,
        mensagem,
        arquivo_atual=arquivo_atual,
        resultado=resultado,
        tipo=tipo,
        titulo=titulo,
        descricao=descricao,
        atualizar_evento=atualizar_evento,
    )


def _selecionar_itens_importacao(
    leituras: dict,
    validacoes: dict,
    id_aluno=None,
    validacoes_manuais: dict | None = None,
) -> tuple[list[tuple[str, dict, dict]], str]:
    id_aluno_filtrado = _texto_limpo(id_aluno)
    manuais = validacoes_manuais if isinstance(validacoes_manuais, dict) else {}
    itens = []
    for nome_imagem, dados_cartao in leituras.items():
        validacao = resolver_validacao_cartao(validacoes, manuais, nome_imagem, dados_cartao)
        id_final = _texto_limpo(validacao.get("id_final"))
        if id_aluno_filtrado and id_final != id_aluno_filtrado:
            continue
        itens.append((nome_imagem, dados_cartao, validacao))
    return itens, id_aluno_filtrado


def contar_alunos_importacao(nome_processamento: str, id_aluno=None) -> int:
    leituras = carregar_leituras(nome_processamento)
    dados_validacao = obter_validacao_cadastral_para_envio(nome_processamento)
    validacoes = dados_validacao.get("validacoes", {})
    validacoes_manuais = carregar_validacao_manual(nome_processamento)
    itens, _ = _selecionar_itens_importacao(
        leituras,
        validacoes,
        id_aluno=id_aluno,
        validacoes_manuais=validacoes_manuais,
    )
    return len(itens)


def contar_imagens_importacao(nome_processamento: str, id_aluno=None) -> int:
    return contar_alunos_importacao(nome_processamento, id_aluno=id_aluno)


def processar_mock_importacao_keepedu(dados: dict) -> tuple[int, dict]:
    if not isinstance(dados, dict):
        return 400, {"success": False, "mensagem": ["Corpo inválido."], "arquivoErros": []}
    alunos = dados.get("alunos", [])
    return 200, {
        "success": True,
        "idAval": dados.get("idAval"),
        "total": len(alunos),
        "importadas": len(alunos),
        "erros": 0,
        "mensagem": ["Sucesso textual."],
        "arquivoErros": []
    }

def _obter_nome_original_limpo(nome_imagem: str, dados_cartao: dict) -> str:
    """
    Garante o retorno do nome do arquivo original limpo,
    removendo 'template_' e forçando a extensão estrita para .jpg
    """
    if not isinstance(dados_cartao, dict):
        dados_cartao = {}
        
    nome_base = dados_cartao.get("arquivo_original") or dados_cartao.get("nome_arquivo")
    
    if not nome_base:
        nome_base = re.sub(r"^template_", "", nome_imagem, flags=re.IGNORECASE)
        
    # FORÇA A EXTENSÃO .JPG
    if nome_base.lower().endswith(".jpeg"):
        nome_base = re.sub(r"\.jpeg$", ".jpg", nome_base, flags=re.IGNORECASE)
        
    return nome_base


def _executar_importacao_respostas_presenciais(
    nome_processamento: str,
    url_destino: str,
    modo: str,
    id_aval=None,
    dia_aval=None,
    usuario_id=None,
    id_aluno=None,
    progresso_callback=None,
    modelo_ia=None,
    origem_envio: str = "validacao",
):
    if not url_destino:
        return {"status": "erro", "mensagem": "URL não configurada no .env."}
    if not KEEPEDU_API_KEY or not KEEPEDU_INSTITUTE:
        return {"status": "erro", "mensagem": "Credenciais da KeepEdu ausentes no .env."}

    leituras = carregar_leituras(nome_processamento)
    dados_validacao = obter_validacao_cadastral_para_envio(nome_processamento)

    validacoes = dados_validacao.get("validacoes", {})
    validacoes_manuais = carregar_validacao_manual(nome_processamento)
    itens_envio, id_aluno_filtrado = _selecionar_itens_importacao(
        leituras,
        validacoes,
        id_aluno=id_aluno,
        validacoes_manuais=validacoes_manuais,
    )
    total_itens = len(itens_envio)

    print("\n===== FILTRO IMPORTAÇÃO =====")
    print("id_aluno:", repr(id_aluno_filtrado))
    print("total itens:", len(itens_envio))

    for item in itens_envio:
        print("item:", item[0])

    print("=============================\n")

    id_aval_final = _detectar_id_aval(dados_validacao, id_aval)
    if not id_aval_final:
        return {"status": "erro", "mensagem": "Informe o ID da avaliação."}

    cabecalhos = _cabecalhos_keepedu()
    payload_base = _payload_base(id_aval_final)
    detalhes = []
    total, importadas = 0, 0
    mensagens, arquivos_erros, pendencias_avaliacao = [], [], []
    validacao_atualizada = False

    total_itens = len(itens_envio)

    _emitir_progresso_importacao(
        progresso_callback,
        percentual=5,
        mensagem="Preparando envio de VETORES (JSON)...",
        resultado={"modo": modo, "id_aluno": id_aluno_filtrado},
        tipo="warning",
        titulo="Envio de Vetores iniciado",
    )

    for indice, (nome_imagem, dados_cartao, validacao) in enumerate(itens_envio, start=1):
        total += 1
        sucesso_item = False
        motivo_item = ""

        if total_itens:
            percentual_item = int(5 + ((indice - 1) / total_itens) * 88)
            _emitir_progresso_importacao(
                progresso_callback,
                percentual=percentual_item,
                mensagem=f"Enviando vetor {indice} de {total_itens}...",
                arquivo_atual=nome_imagem,
                tipo="warning",
                titulo=f"Vetor {indice}/{total_itens}",
                descricao=f"Transmitindo respostas de {nome_imagem}",
            )

        try:
            id_final_envio = _texto_limpo(validacao.get("id_final"))
            barcode_envio = _texto_limpo(validacao.get("codigo_barras_final"))
            if not id_final_envio and not barcode_envio:
                mensagens.append(
                    f"Cartão {nome_imagem} sem ID validado — informe ou revise na Validação Cadastral."
                )
                pendencias_avaliacao.append({
                    "imagem": nome_imagem,
                    "nomeArquivo": _formatar_stem_arquivo_keepedu(
                        _obter_nome_original_limpo(nome_imagem, dados_cartao)
                    ),
                    "motivo": "Cartão sem ID de aluno validado para envio.",
                })
                if total_itens:
                    _emitir_progresso_importacao(
                        progresso_callback,
                        percentual=int(5 + (indice / total_itens) * 88),
                        mensagem=f"Vetor {indice} de {total_itens} ignorado.",
                        arquivo_atual=nome_imagem,
                        tipo="error",
                        titulo=f"Vetor {indice}/{total_itens} pendente",
                        descricao=f"Cartão {nome_imagem} sem ID validado para envio.",
                        atualizar_evento=True,
                    )
                continue

            # CORREÇÃO AQUI: Garante que o payload textual receba o nome limpo do arquivo, sem 'template_'
            nome_original_real = _obter_nome_original_limpo(
                nome_imagem,
                dados_cartao
            )

            # Nome base utilizado pela KeepEdu (mesmo padrão da folha-resposta: hífens)
            nome_arquivo = _formatar_stem_arquivo_keepedu(nome_original_real)

            print("\n===== VETOR =====")
            print("nome_original_real:", nome_original_real)
            print("nome_arquivo_keepedu:", nome_arquivo)
            print("=================\n")

            aluno = _montar_aluno_payload(
                nome_imagem,
                dados_cartao,
                validacao,
                id_aval_final,
                nome_arquivo,
                modelo_ia
            )

            payload_envio = {
                **payload_base,
                "alunos": [aluno]
            }

            if modo == "simulacao":
                status_code, corpo_resposta = processar_mock_importacao_keepedu(payload_envio)
            else:
                status_code, corpo_resposta = _post_keepedu(url_destino, payload_envio, cabecalhos)
                
            print("\n===== RETORNO VETOR =====")
            print("STATUS:", status_code)
            print(json.dumps(corpo_resposta, indent=4, ensure_ascii=False))
            print("========================\n")

            sucesso = _sucesso_resposta_generica(status_code, corpo_resposta)
            fora_avaliacao = _resposta_indica_aluno_fora_avaliacao(corpo_resposta)

            if fora_avaliacao:
                motivo_pendencia = _motivo_pendencia_avaliacao(corpo_resposta, nome_arquivo)
                motivo_item = motivo_pendencia
                registrar_pendencia_importacao(
                    nome_processamento,
                    nome_imagem,
                    "PENDENTE_ALUNO_FORA_AVALIACAO",
                    motivo_pendencia,
                    detalhes={"idAval": id_aval_final, "resposta": corpo_resposta},
                )
                validacao_atualizada = True
                pendencias_avaliacao.append({"imagem": nome_imagem, "nomeArquivo": nome_arquivo, "motivo": motivo_pendencia})
            elif not sucesso:
                mensagens.extend(_mensagens_resposta(corpo_resposta))
                motivo_item = _resumir_motivo_falha_importacao(status_code, corpo_resposta)
            elif sucesso and limpar_pendencia_importacao(nome_processamento, nome_imagem):
                validacao_atualizada = True

            detalhes.append({
                "imagem": nome_imagem,
                "nomeArquivo": nome_arquivo,
                "barcode": aluno.get("barcode", ""),
                "idAluno": aluno.get("idAluno", ""),
                "payload": payload_envio,
                "status_code": status_code,
                "resposta": corpo_resposta,
                "sucesso": sucesso,
                "pendencia_avaliacao": fora_avaliacao,
                "modelo_ia": modelo_ia,
            })

            if sucesso and not fora_avaliacao:
                sucesso_item = True
                importadas += 1
                registrar_envio_keepedu(
                    nome_processamento,
                    nome_imagem,
                    vetores=True,
                    origem=origem_envio,
                    id_aval=id_aval_final,
                )

        except Exception as exc:
            motivo_item = _resumir_motivo_falha_importacao(exc=exc)
            mensagens.append(f"Erro ao processar {nome_imagem}: {exc}")

        if total_itens:
            percentual_item = int(5 + (indice / total_itens) * 88)
            tipo_item = "success" if sucesso_item else "error"
            _emitir_progresso_importacao(
                progresso_callback,
                percentual=percentual_item,
                mensagem=f"Vetor {indice} de {total_itens} processado.",
                arquivo_atual=nome_imagem,
                tipo=tipo_item,
                titulo=f"Vetor {indice}/{total_itens} concluído",
                descricao=_descricao_resultado_item(
                    nome_imagem,
                    sucesso_item,
                    motivo_item,
                    texto_sucesso="Respostas de {nome} enviadas com sucesso.",
                    prefixo_falha="Falha ao enviar respostas de",
                ),
                atualizar_evento=True,
            )

    erros = total - importadas
    status = "ok" if erros == 0 else "parcial"

    relatorio = {
        "status": status,
        "success": status == "ok",
        "modo": modo,
        "url_destino": url_destino,
        "idAval": id_aval_final,
        "total": total,
        "importadas": importadas,
        "erros": erros,
        "mensagem": mensagens,
        "arquivoErros": arquivos_erros,
        "pendencias_avaliacao": pendencias_avaliacao,
        "validacao_atualizada": validacao_atualizada,
        "detalhes": detalhes,
        "modelo_ia": modelo_ia,
    }
    
    _emitir_progresso_importacao(
        progresso_callback,
        percentual=100,
        mensagem=f"Envio de vetores concluído: {importadas}/{total} com sucesso.",
        resultado=relatorio,
        tipo="success" if status == "ok" else "warning",
    )
    relatorio["arquivo_relatorio"] = _salvar_relatorio(nome_processamento, relatorio, modo=modo)
    return relatorio


# Função de Upload Binário Corrigida para injetar o Form-Data sem o prefixo do OMR
def importar_imagens_folha_resposta(
    nome_processamento: str,
    id_aval=None,
    progresso_callback=None,
    id_aluno=None,
    executar_limpeza: bool = False,
    origem_envio: str = "validacao",
):
    if not KEEPEDU_IMPORTAR_FOLHA_RESPOSTA_URL:
        return {"status": "erro", "mensagem": "URL de envio de imagem não configurada no .env."}

    leituras = carregar_leituras(nome_processamento)
    dados_validacao = obter_validacao_cadastral_para_envio(nome_processamento)
    validacoes = dados_validacao.get("validacoes", {})
    id_aval_final = _detectar_id_aval(dados_validacao, id_aval)
    validacoes_manuais = carregar_validacao_manual(nome_processamento)

    itens_para_importar, _ = _selecionar_itens_importacao(
        leituras,
        validacoes,
        id_aluno,
        validacoes_manuais=validacoes_manuais,
    )

    if not itens_para_importar:
        return {"status": "ok", "total": 0, "importadas": 0, "erros": 0, "mensagem": ["Nenhuma imagem selecionada para upload."]}

    cabecalhos_upload = _cabecalhos_keepedu_upload()
    url_folha_resposta = _montar_url_folha_resposta(KEEPEDU_IMPORTAR_FOLHA_RESPOSTA_URL, id_aval_final)
    
    total, importadas, erros = 0, 0, 0
    mensagens = []
    imagens_enviadas_com_sucesso = []
    
    _emitir_progresso_importacao(
        progresso_callback,
        percentual=5,
        mensagem=f"Iniciando upload de {len(itens_para_importar)} imagem(ns) original(is)...",
        tipo="warning",
        titulo="Envio de imagens iniciado",
        descricao=f"{len(itens_para_importar)} imagem(ns) na fila.",
    )

    total_itens = len(itens_para_importar)

    for indice, (nome_imagem, dados_cartao, _) in enumerate(itens_para_importar, start=1):
        total += 1
        sucesso_item = False
        motivo_item = ""

        if total_itens:
            percentual_item = int(5 + ((indice - 1) / total_itens) * 88)
            _emitir_progresso_importacao(
                progresso_callback,
                percentual=percentual_item,
                mensagem=f"Enviando imagem {indice} de {total_itens}...",
                arquivo_atual=nome_imagem,
                tipo="warning",
                titulo=f"Imagem {indice}/{total_itens}",
                descricao=f"Enviando {nome_imagem}",
            )

        nome_original_real = _obter_nome_original_limpo(
            nome_imagem,
            dados_cartao
        )

        nome_arquivo_formatado = _formatar_nome_arquivo_imagem_keepedu(nome_original_real)

        print("\n===== IMAGEM =====")
        print("nome_original_real:", nome_original_real)
        print("nome_arquivo_keepedu:", nome_arquivo_formatado)
        print("=================\n")

        caminho_imagem_original = localizar_imagem_original(
            nome_processamento,
            nome_imagem,
            leituras
        )

        if caminho_imagem_original and caminho_imagem_original.exists():

            status_code, corpo_resposta = _post_keepedu_folha_resposta(
                url_folha_resposta,
                caminho_imagem_original,
                id_aval_final,
                cabecalhos_upload,
                nome_arquivo_limpo=nome_arquivo_formatado
            )

            if _sucesso_resposta_generica(status_code, corpo_resposta):
                sucesso_item = True
                importadas += 1
                imagens_enviadas_com_sucesso.append(nome_original_real)
                registrar_envio_keepedu(
                    nome_processamento,
                    nome_imagem,
                    imagens=True,
                    origem=origem_envio,
                    id_aval=id_aval_final,
                )
            else:
                erros += 1
                mensagens.extend(_mensagens_resposta(corpo_resposta))
                motivo_item = _resumir_motivo_falha_importacao(status_code, corpo_resposta)

        else:
            erros += 1
            motivo_item = f"arquivo físico não localizado ({nome_arquivo_formatado})"
            mensagens.append(f"{nome_imagem}: {motivo_item}")

        if total_itens:
            percentual_item = int(5 + (indice / total_itens) * 88)
            tipo_item = "success" if sucesso_item else "error"
            _emitir_progresso_importacao(
                progresso_callback,
                percentual=percentual_item,
                mensagem=f"Imagem {indice} de {total_itens} processada.",
                arquivo_atual=nome_imagem,
                tipo=tipo_item,
                titulo=f"Imagem {indice}/{total_itens} concluída",
                descricao=_descricao_resultado_item(
                    nome_imagem,
                    sucesso_item,
                    motivo_item,
                    texto_sucesso="Upload de {nome} concluído com sucesso.",
                    prefixo_falha="Falha no upload de",
                ),
                atualizar_evento=True,
            )

    if executar_limpeza and imagens_enviadas_com_sucesso:
        _emitir_progresso_importacao(
            progresso_callback,
            percentual=95,
            mensagem="Finalizando envio e limpando arquivos locais...",
            tipo="warning",
        )
        limpar_pos_importacao_keepedu(nome_processamento, imagens_enviadas=imagens_enviadas_com_sucesso)

    _emitir_progresso_importacao(
        progresso_callback,
        percentual=100,
        mensagem=f"Upload finalizado: {importadas} de {total} imagem(ns).",
        tipo="success" if erros == 0 else "warning",
    )
    return {"status": "ok" if erros == 0 else "parcial", "total": total, "importadas": importadas, "erros": erros, "mensagem": mensagens}


def _validar_cartao_para_envio(nome_processamento: str, nome_imagem: str) -> tuple[dict | None, dict | None, str | None]:
    dados_validacao = gerar_validacao_cadastral(nome_processamento)
    validacao_imagem = dados_validacao.get("validacoes", {}).get(nome_imagem)

    if not validacao_imagem:
        return None, None, "Imagem não encontrada na validação cadastral."

    leituras = carregar_leituras(nome_processamento)
    leitura = leituras.get(nome_imagem) or {}
    if not leitura.get("pontos_cantos"):
        return None, None, "A imagem precisa ser processada antes do envio."

    id_aluno = validacao_imagem.get("id_final")
    if not id_aluno:
        return None, None, "A imagem não possui um ID de aluno validado."

    return validacao_imagem, leitura, None


def _resposta_apenas_imagens(resultado_imagens: dict) -> dict:
    erros_imagens = int(resultado_imagens.get("erros") or 0)
    if erros_imagens > 0:
        mensagens = resultado_imagens.get("mensagem") or []
        if isinstance(mensagens, list):
            texto = " | ".join(str(item) for item in mensagens if item)
        else:
            texto = str(mensagens)
        return {
            "status": "erro",
            "mensagem": texto or "Ocorreram erros durante o envio da imagem.",
            "imagens": resultado_imagens,
        }

    return {
        "status": "ok" if resultado_imagens.get("status") != "erro" else "parcial",
        "imagens": resultado_imagens,
    }


def enviar_imagem_keepedu(nome_processamento: str, nome_imagem: str, id_aval=None) -> dict:
    validacao_imagem, _, erro = _validar_cartao_para_envio(nome_processamento, nome_imagem)
    if erro:
        return {"status": "erro", "mensagem": erro}

    id_aluno = validacao_imagem.get("id_final")

    resultado_imagens = importar_imagens_folha_resposta(
        nome_processamento=nome_processamento,
        id_aval=id_aval,
        id_aluno=id_aluno,
        executar_limpeza=False,
    )
    return _resposta_apenas_imagens(resultado_imagens)


def enviar_todas_imagens_keepedu(nome_processamento: str, id_aval=None) -> dict:
    leituras = carregar_leituras(nome_processamento)
    dados_validacao = gerar_validacao_cadastral(nome_processamento)
    validacoes = dados_validacao.get("validacoes", {})

    elegiveis = [
        nome_imagem
        for nome_imagem, leitura in leituras.items()
        if leitura.get("pontos_cantos") and validacoes.get(nome_imagem, {}).get("id_final")
    ]

    if not elegiveis:
        return {
            "status": "erro",
            "mensagem": "Nenhuma imagem processada com ID validado para envio.",
            "total": 0,
            "importadas": 0,
            "erros": 0,
        }

    resultado_imagens = importar_imagens_folha_resposta(
        nome_processamento=nome_processamento,
        id_aval=id_aval,
        id_aluno=None,
        executar_limpeza=True,
    )

    erros = int(resultado_imagens.get("erros") or 0)
    status = "ok" if erros == 0 else ("parcial" if int(resultado_imagens.get("importadas") or 0) > 0 else "erro")

    return {
        "status": status,
        "total": int(resultado_imagens.get("total") or 0),
        "importadas": int(resultado_imagens.get("importadas") or 0),
        "erros": erros,
        "mensagem": resultado_imagens.get("mensagem") or [],
        "imagens": resultado_imagens,
    }


def enviar_cartao_keepedu(nome_processamento: str, nome_imagem: str, id_aval=None, modelo_ia=None) -> dict:
    validacao_imagem, _, erro = _validar_cartao_para_envio(nome_processamento, nome_imagem)
    if erro:
        return {"status": "erro", "mensagem": erro}

    id_aluno = validacao_imagem.get("id_final")

    resultado_vetores = importar_respostas_presenciais(
        nome_processamento,
        id_aval=id_aval,
        id_aluno=id_aluno,
        modelo_ia=modelo_ia,
        origem_envio="correcao",
    )

    if resultado_vetores.get("status") == "erro":
        return {"status": "erro", "mensagem": resultado_vetores.get("mensagem"), "vetores": resultado_vetores}

    resultado_imagens = importar_imagens_folha_resposta(
        nome_processamento=nome_processamento,
        id_aval=id_aval,
        id_aluno=id_aluno,
        executar_limpeza=False,
        origem_envio="correcao",
    )

    erros_vetores = int(resultado_vetores.get("erros") or 0)
    erros_imagens = int(resultado_imagens.get("erros") or 0)
    if erros_vetores > 0 or erros_imagens > 0:
        mensagens = []
        for origem in (resultado_vetores, resultado_imagens):
            bloco = origem.get("mensagem") or []
            if isinstance(bloco, list):
                mensagens.extend(str(item) for item in bloco if item)
            elif bloco:
                mensagens.append(str(bloco))
        return {
            "status": "erro",
            "mensagem": " | ".join(mensagens) or "Ocorreram erros durante o envio.",
            "vetores": resultado_vetores,
            "imagens": resultado_imagens,
        }

    sucesso_geral = resultado_vetores.get("status") != "erro" and resultado_imagens.get("status") != "erro"
    return {
        "status": "ok" if sucesso_geral else "parcial",
        "vetores": resultado_vetores,
        "imagens": resultado_imagens,
    }


def importar_respostas_presenciais(
    nome_processamento: str,
    id_aval=None,
    dia_aval=None,
    usuario_id=None,
    id_aluno=None,
    progresso_callback=None,
    modelo_ia=None,
    origem_envio: str = "validacao",
):
    return _executar_importacao_respostas_presenciais(
        nome_processamento=nome_processamento, url_destino=KEEPEDU_IMPORTAR_RESPOSTAS_URL, modo="importacao",
        id_aval=id_aval, dia_aval=dia_aval, usuario_id=usuario_id, id_aluno=id_aluno, progresso_callback=progresso_callback, modelo_ia=modelo_ia,
        origem_envio=origem_envio,
    )


def simular_importacao_respostas_presenciais(
    nome_processamento: str,
    id_aval=None,
    dia_aval=None,
    usuario_id=None,
    id_aluno=None,
    progresso_callback=None,
    modelo_ia=None,
    origem_envio: str = "validacao",
):
    return _executar_importacao_respostas_presenciais(
        nome_processamento=nome_processamento, url_destino=KEEPEDU_SIMULAR_IMPORTAR_RESPOSTAS_URL, modo="simulacao",
        id_aval=id_aval, dia_aval=dia_aval, usuario_id=usuario_id, id_aluno=id_aluno, progresso_callback=progresso_callback, modelo_ia=modelo_ia,
        origem_envio=origem_envio,
    )


def caminho_lista_frequencia(nome_processamento: str) -> Path:
    caminho_leitura = caminho_leituras(nome_processamento)
    return caminho_leitura.parent / "lista_frequencia_keepedu.pdf"


def salvar_lista_frequencia_pdf(nome_processamento: str, conteudo: bytes) -> Path:
    caminho = caminho_lista_frequencia(nome_processamento)
    caminho.parent.mkdir(parents=True, exist_ok=True)
    with open(caminho, "wb") as arquivo:
        arquivo.write(conteudo)
    return caminho


def caminho_relatorio_frequencia(nome_processamento: str) -> Path:
    caminho_leitura = caminho_leituras(nome_processamento)
    return caminho_leitura.parent / "frequencia_keepedu.json"


def _post_keepedu_lista_frequencia(
    url_destino: str,
    caminho_arquivo: Path,
    id_simulado,
    cabecalhos: dict,
) -> tuple[int, dict]:
    content_type = mimetypes.guess_type(caminho_arquivo.name)[0] or "application/pdf"

    with open(caminho_arquivo, "rb") as arquivo:
        resposta_api = requests.post(
            url_destino,
            data={"idSimulado": str(id_simulado)},
            files={
                "lista_de_frequencia": (
                    caminho_arquivo.name,
                    arquivo,
                    content_type,
                )
            },
            headers=cabecalhos,
            timeout=KEEPEDU_IMPORTAR_TIMEOUT_SECONDS,
        )

    print("\n========== KEEPEDU FREQUÊNCIA ==========")
    print("URL:", url_destino)
    print("ID SIMULADO:", id_simulado)
    print("ARQUIVO:", caminho_arquivo.name)
    print("STATUS:", resposta_api.status_code)
    print("BODY:", resposta_api.text)
    print("========================================\n")

    status_code = resposta_api.status_code
    try:
        corpo_resposta = resposta_api.json()
    except ValueError:
        corpo_resposta = {
            "success": 200 <= status_code < 300,
            "mensagem": [_resumir_resposta_http_nao_json(status_code, resposta_api, "Importação de frequência")],
        }
    return status_code, corpo_resposta


def importar_lista_frequencia_keepedu(
    nome_processamento: str,
    id_simulado=None,
    caminho_arquivo: str | Path | None = None,
    progresso_callback=None,
) -> dict:
    if not KEEPEDU_IMPORTAR_LISTA_FREQUENCIA_URL:
        return {
            "status": "erro",
            "mensagem": "KEEPEDU_IMPORTAR_LISTA_FREQUENCIA_URL não está configurada.",
        }

    _emitir_progresso_importacao(
        progresso_callback,
        percentual=8,
        mensagem="Validando arquivo de frequência...",
        tipo="warning",
        titulo="Envio de frequência iniciado",
        descricao="Preparando o PDF para transmissão.",
    )

    dados_validacao = obter_validacao_cadastral_para_envio(nome_processamento)
    id_simulado_final = _detectar_id_aval(dados_validacao, id_simulado)

    if not id_simulado_final:
        return {
            "status": "erro",
            "mensagem": "Não foi possível identificar o ID do simulado para o envio de frequências.",
        }

    if not caminho_arquivo:
        return {
            "status": "erro",
            "mensagem": "Informe o arquivo PDF da lista de frequência.",
        }

    caminho_path = Path(caminho_arquivo)
    if not caminho_path.exists():
        return {
            "status": "erro",
            "mensagem": "Arquivo de frequência não encontrado.",
        }

    if caminho_path.suffix.lower() != ".pdf":
        return {
            "status": "erro",
            "mensagem": "A lista de frequência deve estar em formato PDF.",
        }

    _emitir_progresso_importacao(
        progresso_callback,
        percentual=35,
        mensagem="Enviando PDF para a KeepEdu...",
        arquivo_atual=caminho_path.name,
        tipo="warning",
        titulo="Transmitindo lista",
        descricao=f"Enviando {caminho_path.name} ao simulado {id_simulado_final}.",
    )

    status_code, corpo_resposta = _post_keepedu_lista_frequencia(
        KEEPEDU_IMPORTAR_LISTA_FREQUENCIA_URL,
        caminho_path,
        id_simulado_final,
        _cabecalhos_keepedu_upload(),
    )

    sucesso = _sucesso_resposta_generica(status_code, corpo_resposta)
    mensagens = _mensagens_resposta(corpo_resposta)
    mensagem_final = mensagens[0] if mensagens else (
        f"Lista de frequência ({caminho_path.name}) enviada com sucesso."
        if sucesso
        else "A KeepEdu recusou o envio da lista de frequência."
    )
    _emitir_progresso_importacao(
        progresso_callback,
        percentual=100,
        mensagem=mensagem_final,
        arquivo_atual=caminho_path.name,
        tipo="success" if sucesso else "error",
        titulo="Envio concluído" if sucesso else "Falha no envio",
        descricao=mensagem_final,
    )
    relatorio = {
        "status": "ok" if sucesso else "erro",
        "idSimulado": id_simulado_final,
        "arquivo": str(caminho_path),
        "nome_arquivo": caminho_path.name,
        "http_status": status_code,
        "resposta_keepedu": corpo_resposta,
        "mensagem": mensagem_final,
    }

    caminho_relatorio = caminho_relatorio_frequencia(nome_processamento)
    caminho_relatorio.parent.mkdir(parents=True, exist_ok=True)
    with open(caminho_relatorio, "w", encoding="utf-8") as arquivo:
        json.dump(relatorio, arquivo, ensure_ascii=False, indent=4)

    return relatorio