"""Consulta e geração do relatório PDF dos cards do Kanban."""
from __future__ import annotations

from datetime import datetime
from io import BytesIO
from pathlib import Path
from re import sub
from unicodedata import normalize

from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
from reportlab.platypus import Image, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle

from app.kanban import listar_cards, listar_etapas
from app.time_utils import agora_local


ROTULOS_PRAZO = {"no_prazo": "No prazo", "atrasado": "Atrasado"}
LOGO_KEEPEDU = Path(__file__).resolve().parent.parent / "assets" / "logo-gestor.png"


class _CanvasComTotalPaginas(canvas.Canvas):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._estados_paginas = []

    def showPage(self):
        self._estados_paginas.append(dict(self.__dict__))
        self._startPage()

    def save(self):
        total = len(self._estados_paginas)
        for estado in self._estados_paginas:
            self.__dict__.update(estado)
            self.setFont("Helvetica", 7)
            self.setFillColor(colors.HexColor("#607087"))
            self.drawString(9 * mm, 8 * mm, "Relatório gerado automaticamente pelo sistema KeepEdu")
            self.drawRightString(landscape(A4)[0] - 9 * mm, 8 * mm, f"Página {self._pageNumber} de {total}")
            super().showPage()
        super().save()


def _texto(valor) -> str:
    return str(valor or "").strip()


def _data_br(valor: str) -> str:
    texto = _texto(valor)
    if len(texto) >= 10 and texto[4:5] == "-":
        return f"{texto[8:10]}/{texto[5:7]}/{texto[:4]}"
    return texto or "—"


def _situacao(card: dict) -> str:
    if card.get("etapa") == "concluido":
        return "Concluído"
    return ROTULOS_PRAZO.get(_texto(card.get("status_prazo")), "No prazo")


def _rotulos_etapas() -> dict[str, str]:
    return {str(item.get("id")): _texto(item.get("titulo")) for item in listar_etapas(incluir_inativas=True)}


def _normalizar(valor: str) -> str:
    return normalize("NFKD", _texto(valor)).encode("ascii", "ignore").decode().casefold()


def consultar_cards_relatorio(filtros: dict[str, str]) -> tuple[list[dict], dict[str, str], dict[str, str]]:
    """Filtra cards existentes; não cria nem replica dados no banco."""
    filtros = {chave: _texto(valor) for chave, valor in filtros.items() if _texto(valor)}
    etapas = _rotulos_etapas()

    def corresponde(card: dict) -> bool:
        recebimento = _texto(card.get("data_entrega"))[:10]
        if filtros.get("data_inicio") and recebimento < filtros["data_inicio"]:
            return False
        if filtros.get("data_fim") and recebimento > filtros["data_fim"]:
            return False
        campos = {
            "unidade": card.get("unidade"),
            "serie": card.get("serie"), "turma": card.get("turma"),
            "responsavel_entrega": card.get("responsavel_entrega"),
            "responsavel_correcao": card.get("responsavel_correcao"), "etapa": card.get("etapa"),
        }
        for nome, valor in campos.items():
            if filtros.get(nome) and _normalizar(valor) != _normalizar(filtros[nome]):
                return False
        if filtros.get("avaliacao"):
            avaliacao = _normalizar(filtros["avaliacao"])
            if avaliacao not in {_normalizar(card.get("id_avaliacao")), _normalizar(card.get("titulo"))}:
                return False
        if filtros.get("situacao") and _normalizar(_situacao(card)) != _normalizar(filtros["situacao"]):
            return False
        return True

    return [card for card in listar_cards() if corresponde(card)], filtros, etapas


def _nome_arquivo(filtros: dict[str, str]) -> str:
    data = agora_local().strftime("%Y-%m-%d")
    avaliacao = _texto(filtros.get("avaliacao"))
    if avaliacao:
        slug = sub(r"[^a-z0-9]+", "-", _normalizar(avaliacao)).strip("-")[:60]
        if slug:
            return f"relatorio-lotes-{slug}-{data}.pdf"
    return f"relatorio-lotes-{data}.pdf"


def gerar_relatorio_pdf(filtros: dict[str, str]) -> tuple[bytes, str]:
    cards, filtros, etapas = consultar_cards_relatorio(filtros)
    total_cartoes = sum(int(card.get("total_cartoes") or 0) for card in cards)
    concluidos = sum(card.get("etapa") == "concluido" for card in cards)
    atrasados = sum(_situacao(card) == "Atrasado" for card in cards)
    em_andamento = len(cards) - concluidos

    buffer = BytesIO()
    doc = SimpleDocTemplate(buffer, pagesize=landscape(A4), leftMargin=9 * mm, rightMargin=9 * mm, topMargin=12 * mm, bottomMargin=14 * mm)
    estilos = getSampleStyleSheet()
    titulo = ParagraphStyle("titulo", parent=estilos["Heading1"], fontName="Helvetica-Bold", fontSize=16, leading=20, textColor=colors.white, spaceAfter=4)
    meta = ParagraphStyle("meta", parent=estilos["Normal"], fontSize=8, leading=11, textColor=colors.white)
    celula = ParagraphStyle("celula", parent=estilos["Normal"], fontSize=6.2, leading=7.5, wordWrap="CJK")
    cabecalho = ParagraphStyle("cabecalho", parent=celula, fontName="Helvetica-Bold", textColor=colors.white, alignment=TA_CENTER)
    historia = [Paragraph("Colégio Propósito | KeepEdu", meta), Paragraph("Relatório de Processamento de Lotes", titulo)]
    periodo = f"{_data_br(filtros.get('data_inicio'))} a {_data_br(filtros.get('data_fim'))}" if filtros.get("data_inicio") or filtros.get("data_fim") else "Todos os períodos"
    filtros_texto = "; ".join(f"{nome.replace('_', ' ').title()}: {valor}" for nome, valor in filtros.items() if nome not in {"data_inicio", "data_fim"}) or "Nenhum"
    historia += [Paragraph(f"<b>Período:</b> {periodo} &nbsp;&nbsp; <b>Filtros aplicados:</b> {filtros_texto} &nbsp;&nbsp; <b>Gerado em:</b> {agora_local().strftime('%d/%m/%Y às %H:%M')}", meta), Spacer(1, 5)]
    resumo = [["Total de lotes", "Total de cartões", "Em andamento", "Concluídos", "Atrasados"], [str(len(cards)), f"{total_cartoes:,}".replace(",", "."), str(em_andamento), str(concluidos), str(atrasados)]]
    logo = Image(str(LOGO_KEEPEDU), width=62 * mm, height=18 * mm, kind="proportional")
    bloco_cabecalho = Table([[[historia[0], historia[1], historia[2]], logo]], colWidths=[543, 227])
    bloco_cabecalho.setStyle(TableStyle([
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("ALIGN", (1, 0), (1, 0), "RIGHT"),
        ("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#007bff")),
        ("LEFTPADDING", (0, 0), (0, 0), 10),
        ("LEFTPADDING", (1, 0), (1, 0), 0),
        ("RIGHTPADDING", (0, 0), (0, 0), 0),
        ("RIGHTPADDING", (1, 0), (1, 0), 10),
        ("TOPPADDING", (0, 0), (-1, -1), 6),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 6),
    ]))
    historia = [bloco_cabecalho]

    # A soma é igual à largura da tabela de lotes (770 pt), alinhando as duas
    # margens em toda a página.
    tabela_resumo = Table(resumo, colWidths=[154] * 5)
    tabela_resumo.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#007bff")), ("TEXTCOLOR", (0, 0), (-1, -1), colors.white), ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), ("FONTNAME", (0, 1), (-1, 1), "Helvetica-Bold"), ("ALIGN", (0, 0), (-1, -1), "CENTER"), ("FONTSIZE", (0, 0), (-1, -1), 8), ("GRID", (0, 0), (-1, -1), .25, colors.HexColor("#8ec5ff")), ("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5)]))
    historia += [tabela_resumo, Spacer(1, 8)]
    colunas = ["ID", "Avaliação", "Unidade", "Série", "Turma", "Recebimento", "Previsão", "Cartões", "Resp. entrega", "Resp. correção", "Etapa", "Situação"]
    linhas = [[Paragraph(item, cabecalho) for item in colunas]]
    for card in cards:
        valores = [card.get("id_avaliacao") or "—", card.get("titulo") or "—", card.get("unidade") or "—", card.get("serie") or "—", card.get("turma") or "—", _data_br(card.get("data_entrega")), _data_br(card.get("data_previsao_entrega")), str(card.get("total_cartoes") or 0), card.get("responsavel_entrega") or "—", card.get("responsavel_correcao") or "—", etapas.get(card.get("etapa"), card.get("etapa") or "—"), f"{_situacao(card)} ({int(card.get('progresso') or 0)}%)"]
        linhas.append([Paragraph(_texto(valor), celula) for valor in valores])
    # Larguras em pontos, ajustadas à área útil do A4 paisagem para reduzir
    # quebras de texto sem sacrificar os campos operacionais da tabela.
    tabela = Table(
        linhas,
        # Ocupa a largura útil do A4 em paisagem. Priorizamos "Turma", que
        # costuma ter várias descrições e antes ficava excessivamente estreita.
        colWidths=[30, 90, 60, 48, 95, 55, 55, 38, 75, 75, 85, 64],
        repeatRows=1,
    )
    tabela.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#007bff")), ("GRID", (0, 0), (-1, -1), .2, colors.HexColor("#c9d8e8")), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f6f9fc")]), ("LEFTPADDING", (0, 0), (-1, -1), 3), ("RIGHTPADDING", (0, 0), (-1, -1), 3), ("TOPPADDING", (0, 0), (-1, -1), 3), ("BOTTOMPADDING", (0, 0), (-1, -1), 3)]))
    historia.append(tabela)

    doc.build(historia, canvasmaker=_CanvasComTotalPaginas)
    return buffer.getvalue(), _nome_arquivo(filtros)
