"""
Contexto da avaliação/simulado selecionado no Painel e exibição nas telas de lote.
"""
from __future__ import annotations

import json
import os
import re
from pathlib import Path
from typing import Any

from app.keepedu_avaliacoes import (
    buscar_listagem_avaliacoes,
    obter_avaliacao_por_id,
)
from app.validacao_cadastral import extrair_id_prova_do_codigo, quebrar_codigo_barras
from app.settings import PASTA_PROCESSAMENTOS

META_AVALIACAO_FILENAME = "meta_avaliacao.json"

_CAMPOS_META_API = (
    "tipo_prova",
    "nome",
    "nome_simulado",
    "tipo_prova_id",
    "bimestre",
    "etapa_prova",
    "estilo_prova",
    "series",
    "disciplina",
    "slug_disciplina",
    "inicio",
    "termino",
    "ano_letivo",
    "professor",
)

SESSION_KEY_ID = "avaliacao_id"
SESSION_KEY_NOME = "avaliacao_nome"
SESSION_KEY_META = "avaliacao_meta_json"

# Fallback se a API estiver indisponível
SIMULADOS_DISPONIVEIS: list[dict[str, str]] = [
    {"id": "12600", "nome": "Simulado Bernoulli", "tipo_prova": "Simulado Bernoulli"},
    {"id": "12479", "nome": "Avaliação padrão KeepEdu", "tipo_prova": "Avaliação padrão KeepEdu"},
]


def _meta_da_sessao(session: dict) -> dict[str, str]:
    bruto = session.get(SESSION_KEY_META)
    if not bruto:
        return {}
    if isinstance(bruto, dict):
        return {str(k): str(v) for k, v in bruto.items() if v is not None}
    try:
        dados = json.loads(str(bruto))
    except Exception:
        return {}
    if not isinstance(dados, dict):
        return {}
    return {str(k): str(v) for k, v in dados.items() if v is not None and str(v).strip()}


def _salvar_meta_na_sessao(session: dict, meta: dict[str, str]) -> None:
    session[SESSION_KEY_META] = json.dumps(meta, ensure_ascii=False)


def _fallback_local() -> list[dict[str, str]]:
    itens = []
    for item in SIMULADOS_DISPONIVEIS:
        copia = dict(item)
        copia.setdefault("tipo_prova", copia.get("nome", ""))
        copia.setdefault("rotulo_select", f"[{copia.get('id')}] · {copia.get('nome')}")
        itens.append(copia)
    return itens


def listar_simulados_disponiveis(*, force: bool = False) -> list[dict[str, str]]:
    resultado = buscar_listagem_avaliacoes(force=force)
    itens = resultado.get("avaliacoes") or []
    if itens:
        return [dict(item) for item in itens]
    if resultado.get("erro"):
        return _fallback_local()
    return _fallback_local()


def obter_simulado_por_id(id_avaliacao: str, *, force: bool = False) -> dict[str, str] | None:
    id_texto = str(id_avaliacao or "").strip()
    if not id_texto:
        return None

    item = obter_avaliacao_por_id(id_texto, force=force)
    if item:
        return dict(item)

    for local in SIMULADOS_DISPONIVEIS:
        if str(local.get("id", "")).strip() == id_texto:
            copia = dict(local)
            copia.setdefault("tipo_prova", copia.get("nome", ""))
            return copia
    return None


def contexto_avaliacao_da_sessao(session: dict) -> dict[str, Any]:
    id_avaliacao = str(session.get(SESSION_KEY_ID) or "").strip()
    nome = str(session.get(SESSION_KEY_NOME) or "").strip()
    meta = _meta_da_sessao(session)

    if id_avaliacao and not meta:
        simulado = obter_simulado_por_id(id_avaliacao)
        if simulado:
            meta = dict(simulado)
            _salvar_meta_na_sessao(session, meta)

    if id_avaliacao and not nome:
        nome = meta.get("tipo_prova") or meta.get("nome") or ""
        if not nome:
            simulado = obter_simulado_por_id(id_avaliacao)
            nome = simulado.get("nome", "") if simulado else ""

    ctx: dict[str, Any] = {
        "id_avaliacao": id_avaliacao,
        "nome_simulado": nome,
        "configurado": bool(id_avaliacao),
        **{k: meta.get(k, "") for k in (
            "tipo_prova", "bimestre", "etapa_prova", "estilo_prova",
            "series", "disciplina", "inicio", "termino", "ano_letivo",
        )},
    }
    if meta.get("tipo_prova"):
        ctx["tipo_prova"] = meta["tipo_prova"]
    elif nome:
        ctx["tipo_prova"] = nome
    return ctx


def descricao_avaliacao_da_sessao(session: dict) -> str:
    ctx = contexto_avaliacao_da_sessao(session)
    if ctx.get("tipo_prova"):
        return str(ctx["tipo_prova"])
    if ctx.get("nome_simulado"):
        return str(ctx["nome_simulado"])
    if ctx.get("id_avaliacao"):
        return f"Avaliação {ctx['id_avaliacao']}"
    return "Nenhum simulado selecionado"


def aplicar_contexto_na_sessao(session: dict, id_avaliacao: str, nome_simulado: str = "") -> dict[str, Any]:
    id_texto = str(id_avaliacao or "").strip()
    if not id_texto:
        raise ValueError("Informe o ID da avaliação.")

    simulado = obter_simulado_por_id(id_texto)
    nome_final = str(nome_simulado or "").strip()

    if simulado:
        meta = dict(simulado)
        if not nome_final:
            nome_final = meta.get("tipo_prova") or meta.get("nome", "")
    else:
        meta = {"id": id_texto, "tipo_prova": nome_final or f"Avaliação {id_texto}", "nome": nome_final or f"Avaliação {id_texto}"}
        if not nome_final:
            nome_final = meta["tipo_prova"]

    permitido, mensagem = verificar_avaliacao_na_plataforma(id_texto)
    if not permitido:
        raise ValueError(mensagem or "Avaliação não autorizada pela plataforma.")

    session[SESSION_KEY_ID] = id_texto
    session[SESSION_KEY_NOME] = nome_final
    _salvar_meta_na_sessao(session, meta)

    return contexto_avaliacao_da_sessao(session)


def limpar_contexto_na_sessao(session: dict) -> dict[str, Any]:
    session.pop(SESSION_KEY_ID, None)
    session.pop(SESSION_KEY_NOME, None)
    session.pop(SESSION_KEY_META, None)
    return contexto_avaliacao_da_sessao(session)


def verificar_avaliacao_na_plataforma(id_avaliacao: str) -> tuple[bool, str]:
    id_texto = str(id_avaliacao or "").strip()
    if not id_texto:
        return False, "ID da avaliação inválido."

    from app.settings import KEEPEDU_LISTAGEM_AVALIACAO_URL

    if KEEPEDU_LISTAGEM_AVALIACAO_URL:
        item = obter_avaliacao_por_id(id_texto)
        if item:
            return True, ""
        resultado = buscar_listagem_avaliacoes()
        if resultado.get("success") and resultado.get("avaliacoes"):
            return False, f"Avaliação {id_texto} não encontrada na listagem KeepEdu."
        if resultado.get("erro"):
            item_local = None
            for local in SIMULADOS_DISPONIVEIS:
                if str(local.get("id", "")).strip() == id_texto:
                    item_local = local
                    break
            if item_local:
                return True, ""

    url_template = os.getenv("KEEPEDU_VALIDAR_AVALIACAO_URL", "").strip()
    if not url_template:
        return True, ""

    url = url_template.replace("{id_aval}", id_texto)

    try:
        import requests

        metodo = os.getenv("KEEPEDU_VALIDAR_AVALIACAO_METODO", "GET").strip().upper()
        timeout = float(os.getenv("KEEPEDU_VALIDAR_AVALIACAO_TIMEOUT", "12"))

        with requests.Session() as client:
            if metodo == "POST":
                resposta = client.post(url, json={"idAval": id_texto}, timeout=timeout)
            else:
                resposta = client.get(url, timeout=timeout)

        if resposta.status_code >= 400:
            return False, f"API recusou a avaliação {id_texto} (HTTP {resposta.status_code})."

        try:
            dados = resposta.json()
        except Exception:
            dados = {}

        if isinstance(dados, dict):
            if dados.get("permitido") is False or dados.get("ativo") is False:
                return False, dados.get("mensagem") or f"Avaliação {id_texto} indisponível na plataforma."
            if dados.get("permitido") is True or dados.get("ativo") is True:
                return True, ""

        return True, ""
    except Exception as erro:
        return False, f"Não foi possível validar a avaliação na API: {erro}"


def extrair_id_avaliacao_do_nome_arquivo(nome_arquivo: str) -> str:
    stem = Path(str(nome_arquivo or "").strip()).stem
    if stem.lower().startswith("template_"):
        stem = stem[9:]
    match = re.match(r"^(\d+)_", stem)
    return match.group(1) if match else ""


def resolver_id_avaliacao_esperado_lote(nome_processamento: str, leituras: dict | None = None) -> str:
    from app.validacao_cadastral import detectar_id_prova_do_processamento

    meta = carregar_meta_avaliacao_do_lote(nome_processamento)
    id_av = str(meta.get("id_avaliacao") or meta.get("id") or "").strip()
    if id_av:
        return id_av

    leituras = leituras if isinstance(leituras, dict) else {}
    return str(detectar_id_prova_do_processamento(leituras, nome_processamento) or "").strip()


def lote_prioriza_id_configurado(nome_processamento: str) -> bool:
    """True quando o lote tem ID de avaliação definido no painel (meta do lote)."""
    meta = carregar_meta_avaliacao_do_lote(nome_processamento)
    if str(meta.get("id_configurado_no_painel") or "").strip().lower() in {"1", "true", "sim", "yes"}:
        return True
    return bool(str(meta.get("id_avaliacao") or meta.get("id") or "").strip())


def normalizar_codigo_barras_para_id_configurado(
    codigo_barras: str,
    id_configurado: str,
) -> str:
    """Substitui o ID impresso no cartão pelo ID configurado no painel/lote."""
    id_esperado = str(id_configurado or "").strip()
    codigo = str(codigo_barras or "").strip()
    if not id_esperado or not codigo:
        return codigo

    id_prova, id_aluno = quebrar_codigo_barras(codigo)
    if id_aluno:
        return f"{id_esperado}A{id_aluno}"
    if id_prova:
        return id_esperado
    return codigo


def validar_cartao_para_avaliacao_lote(
    nome_arquivo: str,
    codigo_barras: str,
    id_avaliacao_esperado: str,
    *,
    permitir_id_impresso_divergente: bool = False,
) -> tuple[bool, str]:
    id_esperado = str(id_avaliacao_esperado or "").strip()
    if not id_esperado:
        return True, ""

    id_nome = extrair_id_avaliacao_do_nome_arquivo(nome_arquivo)
    if id_nome and id_nome != id_esperado and not permitir_id_impresso_divergente:
        return (
            False,
            f"Cartão da avaliação {id_nome}, diferente da avaliação selecionada ({id_esperado}). "
            "Processamento bloqueado.",
        )

    return validar_barcode_para_avaliacao_configurada(
        codigo_barras,
        id_esperado,
        permitir_id_impresso_divergente=permitir_id_impresso_divergente,
    )


def validar_barcode_para_avaliacao_configurada(
    codigo_barras: str,
    id_avaliacao_esperado: str,
    *,
    permitir_id_impresso_divergente: bool = False,
) -> tuple[bool, str]:
    id_esperado = str(id_avaliacao_esperado or "").strip()
    if not id_esperado:
        return False, "Configure a avaliação no Painel antes de processar."

    codigo = str(codigo_barras or "").strip().upper()
    if not codigo:
        return False, "Cartão sem código de barras legível."

    id_lido = extrair_id_prova_do_codigo(codigo)
    if not id_lido:
        return False, "Não foi possível extrair o ID da avaliação do código de barras."

    if str(id_lido).strip() != id_esperado:
        if not permitir_id_impresso_divergente:
            return (
                False,
                f"Cartão da avaliação {id_lido}, diferente da avaliação selecionada ({id_esperado}). "
                "Processamento bloqueado.",
            )

    permitido, mensagem_api = verificar_avaliacao_na_plataforma(id_esperado)
    if not permitido:
        return False, mensagem_api or "Avaliação não confirmada na plataforma."

    return True, ""


def contexto_para_processamento(session: dict) -> dict[str, Any]:
    ctx = contexto_avaliacao_da_sessao(session)
    if not ctx.get("configurado"):
        return {}
    meta = _meta_da_sessao(session)
    if not meta:
        meta = {
            k: str(ctx.get(k, "") or "")
            for k in (
                "tipo_prova", "bimestre", "etapa_prova", "estilo_prova",
                "series", "disciplina", "inicio", "termino", "ano_letivo",
            )
            if ctx.get(k)
        }
    meta["id_avaliacao"] = ctx["id_avaliacao"]
    meta.setdefault("tipo_prova", ctx.get("tipo_prova") or ctx.get("nome_simulado") or "")
    meta["id_configurado_no_painel"] = "true"
    return {
        "id_avaliacao_esperado": ctx["id_avaliacao"],
        "nome_simulado": ctx.get("nome_simulado") or ctx.get("tipo_prova") or "",
        "meta_avaliacao": meta,
    }


def caminho_meta_avaliacao_lote(nome_processamento: str) -> Path:
    return PASTA_PROCESSAMENTOS / nome_processamento / META_AVALIACAO_FILENAME


def carregar_meta_avaliacao_do_lote(nome_processamento: str) -> dict[str, str]:
    caminho = caminho_meta_avaliacao_lote(nome_processamento)
    if not caminho.exists():
        return {}
    try:
        dados = json.loads(caminho.read_text(encoding="utf-8"))
    except Exception:
        return {}
    if not isinstance(dados, dict):
        return {}

    meta = {str(k): str(v) for k, v in dados.items() if v is not None and str(v).strip()}
    id_av = meta.get("id_avaliacao") or meta.get("id") or ""
    if id_av:
        meta.setdefault("id_avaliacao", str(id_av).strip())
    nome = meta.get("nome_simulado") or meta.get("tipo_prova") or ""
    if nome:
        meta.setdefault("nome_simulado", nome)
        meta.setdefault("tipo_prova", meta.get("tipo_prova") or nome)
    return meta


def mesclar_meta_com_api_keepedu(
    meta: dict | None,
    id_avaliacao: str = "",
) -> dict[str, str]:
    """
    Completa metadados de exibição com a listagem KeepEdu (bimestre, séries, etc.).
    Campos locais extras (ex.: unidade) são preservados.
    """
    resultado = dict(meta or {})
    id_limpo = str(
        id_avaliacao
        or resultado.get("id_avaliacao")
        or resultado.get("id")
        or ""
    ).strip()
    if not id_limpo:
        return resultado

    item = obter_simulado_por_id(id_limpo)
    if not item:
        resultado.setdefault("id_avaliacao", id_limpo)
        resultado.setdefault("id", id_limpo)
        return resultado

    for campo in _CAMPOS_META_API:
        valor_api = str(item.get(campo) or "").strip()
        if valor_api:
            resultado[campo] = valor_api

    resultado["id_avaliacao"] = id_limpo
    resultado["id"] = id_limpo
    if not str(resultado.get("nome_simulado") or "").strip():
        resultado["nome_simulado"] = (
            str(resultado.get("tipo_prova") or item.get("nome") or "").strip()
        )
    return resultado


def salvar_meta_avaliacao_no_lote(
    nome_processamento: str,
    id_avaliacao: str,
    nome_simulado: str = "",
    meta_extra: dict[str, str] | None = None,
) -> None:
    pasta = PASTA_PROCESSAMENTOS / nome_processamento
    if not pasta.exists():
        return

    meta: dict[str, str] = {}
    if meta_extra:
        meta.update({str(k): str(v) for k, v in meta_extra.items() if v is not None and str(v).strip()})

    id_texto = str(id_avaliacao or meta.get("id_avaliacao") or meta.get("id") or "").strip()
    if id_texto:
        meta["id_avaliacao"] = id_texto
        meta["id"] = id_texto

    nome_final = str(
        nome_simulado
        or meta.get("nome_simulado")
        or meta.get("tipo_prova")
        or ""
    ).strip()
    if nome_final:
        meta["nome_simulado"] = nome_final
        meta.setdefault("tipo_prova", nome_final)

    meta = mesclar_meta_com_api_keepedu(meta, id_texto)

    caminho = pasta / META_AVALIACAO_FILENAME
    caminho.write_text(
        json.dumps(meta, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )


def descricao_avaliacao_para_exibicao(
    session: dict | None,
    nome_processamento: str = "",
) -> str:
    ex = contexto_exibicao_avaliacao(session, nome_processamento=nome_processamento)
    if ex.get("tipo_prova"):
        return str(ex["tipo_prova"])
    if ex.get("nome_simulado"):
        return str(ex["nome_simulado"])
    if ex.get("id_avaliacao"):
        return f"Avaliação {ex['id_avaliacao']}"
    return descricao_avaliacao_da_sessao(session or {})


def contexto_exibicao_avaliacao(
    session: dict | None,
    nome_processamento: str = "",
    id_fallback: str = "",
) -> dict[str, Any]:
    """
    Contexto unificado para o hero (Correção, Validação, Processamento, Painel).
    """
    meta: dict[str, str] = {}
    if nome_processamento:
        meta = carregar_meta_avaliacao_do_lote(nome_processamento)

    if not meta and session:
        ctx_sessao = contexto_avaliacao_da_sessao(session)
        if ctx_sessao.get("id_avaliacao"):
            meta = {
                "id_avaliacao": ctx_sessao["id_avaliacao"],
                "nome_simulado": ctx_sessao.get("nome_simulado", ""),
                "tipo_prova": ctx_sessao.get("tipo_prova") or ctx_sessao.get("nome_simulado", ""),
                **{k: ctx_sessao.get(k, "") for k in (
                    "bimestre", "etapa_prova", "estilo_prova", "series",
                    "disciplina", "inicio", "termino", "ano_letivo",
                )},
            }
            meta_sessao = _meta_da_sessao(session)
            if meta_sessao:
                meta.update(meta_sessao)

    id_avaliacao = (
        meta.get("id_avaliacao")
        or meta.get("id")
        or str(id_fallback or "").strip()
    )
    meta = mesclar_meta_com_api_keepedu(meta, str(id_avaliacao or ""))
    id_avaliacao = meta.get("id_avaliacao") or meta.get("id") or id_avaliacao

    tipo_prova = meta.get("tipo_prova") or meta.get("nome_simulado") or meta.get("nome") or ""
    return {
        "id_avaliacao": id_avaliacao,
        "nome_simulado": meta.get("nome_simulado") or tipo_prova,
        "tipo_prova": tipo_prova,
        "bimestre": meta.get("bimestre", ""),
        "etapa_prova": meta.get("etapa_prova", ""),
        "estilo_prova": meta.get("estilo_prova", ""),
        "series": meta.get("series", ""),
        "disciplina": meta.get("disciplina", ""),
        "inicio": meta.get("inicio", ""),
        "termino": meta.get("termino", ""),
        "ano_letivo": meta.get("ano_letivo", ""),
        "configurado": bool(id_avaliacao),
    }
