from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
from starlette.concurrency import run_in_threadpool

from custos_precificacao import auth, historico, totvs_estrutura as estrutura, totvs_ndc as ndc, totvs_produtos as produtos
from custos_precificacao.api.deps import get_session
from custos_precificacao.api.schemas import (
    CargaTotvsOut,
    ConferenciaOut,
    LadoConferenciaOut,
    LinhaConferenciaOut,
    MensagemTotvsOut,
    MudancaTotvsOut,
    PaiConferenciaOut,
    PaiDetalheConferenciaOut,
    PreviaTotvsOut,
    ResumoConferenciaOut,
)
from custos_precificacao.models import TotvsCarga

router = APIRouter(prefix="/totvs", tags=["totvs"])

XLSX_LIMITE = estrutura.LIMITE_BYTES
LIMITE = estrutura.LIMITE_LISTA


async def _corpo(request: Request) -> bytes:
    tamanho = request.headers.get("content-length")
    if tamanho and tamanho.isdigit() and int(tamanho) > XLSX_LIMITE:
        raise HTTPException(status_code=413, detail=f"Arquivo grande demais; o limite é {XLSX_LIMITE // 1024 // 1024} MB")
    dados = await request.body()
    if not dados:
        raise HTTPException(status_code=422, detail="Nenhum arquivo foi enviado")
    return dados


def _mes(data) -> str:
    return f"{data.year:04d}-{data.month:02d}"


def _previa(plano: estrutura.Plano | ndc.PlanoCustos | produtos.PlanoCadastro, *, gravado: bool, arquivo: str | None) -> PreviaTotvsOut:
    return PreviaTotvsOut(
        ok=plano.ok, gravado=gravado, vigencia=_mes(plano.vigencia), emitido_em=plano.emitido_em, arquivo=arquivo,
        pais=plano.pais, linhas_no_relatorio=plano.linhas_no_relatorio, contagem=plano.contagem,
        n_mudancas=len(plano.mudancas),
        mudancas=[MudancaTotvsOut(objeto=m.objeto, acao=m.acao, codigo=m.codigo, detalhe=m.detalhe) for m in plano.mudancas[:LIMITE]],
        n_avisos=len(plano.avisos), avisos=[MensagemTotvsOut(codigo=c, mensagem=m) for c, m in plano.avisos[:LIMITE]],
        n_erros=len(plano.erros), erros=[MensagemTotvsOut(codigo=c, mensagem=m) for c, m in plano.erros[:LIMITE]],
        nao_vieram=plano.nao_vieram[:LIMITE], n_nao_vieram=getattr(plano, "total_sem_estrutura", len(plano.nao_vieram)), n_sem_custo=len(getattr(plano, "sem_custo", [])), sem_custo=getattr(plano, "sem_custo", [])[:LIMITE],
    )


def _processar(session: Session, dados: bytes, vigencia: str | None, arquivo: str | None, *, gravar: bool) -> PreviaTotvsOut:
    try:
        relatorio = estrutura.ler_relatorio(dados)
        plano = estrutura.planejar(session, relatorio, vigencia)
    except (estrutura.ErroDeRelatorio, historico.ErroDeHistorico) as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc
    gravado = False
    if gravar and plano.ok:
        estrutura.aplicar(
            session, relatorio, plano, arquivo=arquivo, sha256=estrutura.hash_do_arquivo(dados), usuario=session.info.get("usuario")
        )
        gravado = True
    return _previa(plano, gravado=gravado, arquivo=arquivo)


@router.post("/estrutura/previa", response_model=PreviaTotvsOut, dependencies=[Depends(auth.exigir_admin)])
async def previa_estrutura(
    request: Request, vigencia: str | None = None, arquivo: str | None = None, session: Session = Depends(get_session)
) -> PreviaTotvsOut:
    """Lê o relatório e mostra o que seria criado/alterado/removido, com avisos e erros — sem gravar nada."""
    dados = await _corpo(request)
    return await run_in_threadpool(_processar, session, dados, vigencia, arquivo, gravar=False)


@router.post("/estrutura/confirmar", response_model=PreviaTotvsOut, dependencies=[Depends(auth.exigir_admin)])
async def confirmar_estrutura(
    request: Request, vigencia: str | None = None, arquivo: str | None = None, session: Session = Depends(get_session)
) -> PreviaTotvsOut:
    """Revalida e grava, tudo ou nada: com qualquer erro nada é gravado (`gravado` = false)."""
    dados = await _corpo(request)
    return await run_in_threadpool(_processar, session, dados, vigencia, arquivo, gravar=True)


def _processar_ndc(session: Session, dados: bytes, vigencia: str | None, arquivo: str | None, *, gravar: bool) -> PreviaTotvsOut:
    try:
        relatorio = ndc.ler_ndc(dados)
        plano = ndc.planejar_custos(session, relatorio, vigencia)
    except (estrutura.ErroDeRelatorio, historico.ErroDeHistorico) as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc
    gravado = False
    if gravar and plano.ok:
        ndc.aplicar_custos(session, plano, arquivo=arquivo, sha256=estrutura.hash_do_arquivo(dados), usuario=session.info.get("usuario"))
        gravado = True
    return _previa(plano, gravado=gravado, arquivo=arquivo)


@router.post("/ndc/previa", response_model=PreviaTotvsOut, dependencies=[Depends(auth.exigir_admin)])
async def previa_ndc(
    request: Request, vigencia: str | None = None, arquivo: str | None = None, session: Session = Depends(get_session)
) -> PreviaTotvsOut:
    """Lê a NDC e mostra quais custos mudariam (anterior → novo), os itens sem custo e os que não estão nela — sem gravar."""
    dados = await _corpo(request)
    return await run_in_threadpool(_processar_ndc, session, dados, vigencia, arquivo, gravar=False)


@router.post("/ndc/confirmar", response_model=PreviaTotvsOut, dependencies=[Depends(auth.exigir_admin)])
async def confirmar_ndc(
    request: Request, vigencia: str | None = None, arquivo: str | None = None, session: Session = Depends(get_session)
) -> PreviaTotvsOut:
    dados = await _corpo(request)
    return await run_in_threadpool(_processar_ndc, session, dados, vigencia, arquivo, gravar=True)


def _processar_produtos(session: Session, dados: bytes, vigencia: str | None, arquivo: str | None, *, gravar: bool) -> PreviaTotvsOut:
    try:
        itens = produtos.ler_cadastro(dados)
        plano = produtos.planejar_cadastro(session, itens, vigencia)
    except (estrutura.ErroDeRelatorio, historico.ErroDeHistorico) as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc
    gravado = False
    if gravar and plano.ok:
        produtos.aplicar_cadastro(session, plano, arquivo=arquivo, sha256=estrutura.hash_do_arquivo(dados), usuario=session.info.get("usuario"))
        gravado = True
    return _previa(plano, gravado=gravado, arquivo=arquivo)


@router.post("/produtos/previa", response_model=PreviaTotvsOut, dependencies=[Depends(auth.exigir_admin)])
async def previa_produtos(
    request: Request, vigencia: str | None = None, arquivo: str | None = None, session: Session = Depends(get_session)
) -> PreviaTotvsOut:
    """Lê a aba Totvs_Cad_Prod e mostra os nomes que mudariam e os itens de produção do cadastro que ainda não têm estrutura."""
    dados = await _corpo(request)
    return await run_in_threadpool(_processar_produtos, session, dados, vigencia, arquivo, gravar=False)


@router.post("/produtos/confirmar", response_model=PreviaTotvsOut, dependencies=[Depends(auth.exigir_admin)])
async def confirmar_produtos(
    request: Request, vigencia: str | None = None, arquivo: str | None = None, session: Session = Depends(get_session)
) -> PreviaTotvsOut:
    dados = await _corpo(request)
    return await run_in_threadpool(_processar_produtos, session, dados, vigencia, arquivo, gravar=True)


def _carga_out(carga: TotvsCarga, nome: auth.NomesDeAutores) -> CargaTotvsOut:
    return CargaTotvsOut(
        id=carga.id, fonte=carga.fonte, arquivo=carga.arquivo, emitido_em=carga.emitido_em, vigencia=_mes(carga.vigencia),
        carregado_em=carga.carregado_em, carregado_por=nome(carga.carregado_por), resumo=carga.resumo or {},
    )


@router.get("/cargas", response_model=list[CargaTotvsOut])
def listar_cargas(fonte: str | None = None, session: Session = Depends(get_session)) -> list[CargaTotvsOut]:
    consulta = session.query(TotvsCarga)
    if fonte:
        consulta = consulta.filter_by(fonte=fonte)
    nome = auth.NomesDeAutores(session)
    return [_carga_out(c, nome) for c in consulta.order_by(TotvsCarga.id.desc()).limit(50)]


@router.get("/estrutura/conferencia", response_model=ConferenciaOut)
def conferencia(busca: str = "", situacao: str = "", session: Session = Depends(get_session)) -> ConferenciaOut:
    """Último relatório aplicado × formulação do sistema, pai a pai (só as linhas de origem TOTVS entram na comparação)."""
    resultado = estrutura.conferir(session, busca=busca, situacao=situacao)
    ultima = session.query(TotvsCarga).filter_by(fonte="estrutura").order_by(TotvsCarga.id.desc()).first()
    return ConferenciaOut(
        resumo=ResumoConferenciaOut(**resultado["resumo"]),
        pais=[PaiConferenciaOut(**vars(p)) for p in resultado["pais"]],
        carga=_carga_out(ultima, auth.NomesDeAutores(session)) if ultima else None,
    )


@router.get("/estrutura/conferencia/{pai}", response_model=PaiDetalheConferenciaOut)
def conferencia_do_pai(pai: str, session: Session = Depends(get_session)) -> PaiDetalheConferenciaOut:
    detalhe = estrutura.conferir_pai(session, pai)
    if detalhe is None:
        raise HTTPException(status_code=404, detail=f"{pai!r} não está no último relatório aplicado")
    return PaiDetalheConferenciaOut(
        **{k: v for k, v in detalhe.items() if k != "linhas"},
        linhas=[
            LinhaConferenciaOut(
                componente=l.componente, descricao=l.descricao, situacao=l.situacao, diferencas=l.diferencas,
                relatorio=LadoConferenciaOut.model_validate(l.relatorio) if l.relatorio else None,
                sistema=LadoConferenciaOut.model_validate(l.sistema) if l.sistema else None,
            )
            for l in detalhe["linhas"]
        ],
    )
