import csv
from io import BytesIO, StringIO

from docx import Document
from openpyxl import Workbook


def _thesis_reminder_articles(review, filter_mode: str):
    from reviews.models import Article

    q = Article.objects.filter(review_id=review.id, status="A").order_by("title")
    if filter_mode == "highlighted":
        return q.filter(thesis_highlight=True).all()
    if filter_mode == "with_notes":
        return q.filter(
            (Article.thesis_highlight == True)  # noqa: E712
            | (Article.thesis_reminder != "")
            | (Article.thesis_tags != "")
        ).all()
    return q.all()


def export_thesis_reminders_docx(
    review,
    *,
    filter_mode: str = "highlighted",
    include_bibliographic: bool = True,
    include_tags: bool = True,
    include_reminder: bool = True,
) -> BytesIO:
    articles = _thesis_reminder_articles(review, filter_mode)
    doc = Document()
    doc.add_heading(review.title, 0)
    doc.add_heading("Lembretes para a tese", level=1)
    doc.add_paragraph(
        "Anotações pessoais de relevância — não fazem parte da extração formal da revisão."
    )
    if not articles:
        doc.add_paragraph("Nenhum estudo encontrado com os filtros selecionados.")
    for article in articles:
        doc.add_heading(article.title or f"Artigo #{article.id}", level=2)
        if include_bibliographic:
            meta = []
            if article.authors:
                meta.append(article.authors)
            if article.year:
                meta.append(article.year)
            if article.source:
                meta.append(article.source.name)
            if article.doi:
                meta.append(f"DOI: {article.doi}")
            if meta:
                doc.add_paragraph(" · ".join(meta))
        if include_tags and article.thesis_tag_list:
            doc.add_paragraph("Tags: " + ", ".join(article.thesis_tag_list))
        if include_reminder and article.thesis_reminder:
            doc.add_paragraph(article.thesis_reminder)
        elif include_reminder and article.thesis_highlight and not article.thesis_reminder:
            doc.add_paragraph("(Destaque sem texto de lembrete)")
    output = BytesIO()
    doc.save(output)
    output.seek(0)
    return output


def export_articles_xlsx(review) -> BytesIO:
    wb = Workbook()
    ws = wb.active
    ws.title = "Articles"
    headers = [
        "ID", "Título", "Autores", "Ano", "Periódico", "DOI", "Base de dados", "Status",
        "Palavras-chave autor", "Palavras-chave index", "ISSN", "Idioma", "PMID",
        "Comentários", "Criado por", "Atualizado em",
    ]
    ws.append(headers)
    for article in review.articles.all():
        ws.append([
            article.id,
            article.title,
            article.authors,
            article.year,
            article.journal,
            article.doi,
            article.source.name if article.source else "",
            article.status_label,
            article.author_keywords,
            article.index_keywords,
            article.issn,
            article.language,
            article.pmid,
            article.comments,
            article.created_by.name if article.created_by else "",
            article.updated_at.strftime("%Y-%m-%d %H:%M") if article.updated_at else "",
        ])
    output = BytesIO()
    wb.save(output)
    output.seek(0)
    return output


def export_extraction_xlsx(review) -> BytesIO:
    wb = Workbook()
    ws = wb.active
    ws.title = "Data Extraction"
    fields = list(review.extraction_fields.all())
    headers = ["Article ID", "Title"] + [f.description for f in fields]
    ws.append(headers)
    for article in review.get_final_selection_articles():
        row = [article.id, article.title]
        for field in fields:
            extraction = next(
                (e for e in article.extractions.all() if e.field_id == field.id), None
            )
            if extraction is None:
                row.append("")
            elif field.field_type == "S":
                row.append(", ".join(v.value for v in extraction.select_values.all()))
            else:
                row.append(extraction.value)
        ws.append(row)
    output = BytesIO()
    wb.save(output)
    output.seek(0)
    return output


def export_protocol_docx(review) -> BytesIO:
    doc = Document()
    doc.add_heading(review.title, 0)
    doc.add_heading("Objective", level=1)
    doc.add_paragraph(review.objective or "—")

    doc.add_heading("PICOC", level=1)
    for label, value in [
        ("Population", review.population),
        ("Intervention", review.intervention),
        ("Comparison", review.comparison),
        ("Outcome", review.outcome),
        ("Context", review.context),
    ]:
        doc.add_heading(label, level=2)
        doc.add_paragraph(value or "—")

    doc.add_heading("Research Questions", level=1)
    for q in review.questions.all():
        doc.add_paragraph(q.question, style="List Number")

    doc.add_heading("Hipóteses", level=1)
    doc.add_heading("Hipóteses fortes", level=2)
    strong = [h for h in review.hypotheses.all() if h.hypothesis_type == "S"]
    if strong:
        for i, h in enumerate(strong, 1):
            doc.add_paragraph(f"HF{i}: {h.statement}", style="List Number")
    else:
        doc.add_paragraph("—")
    doc.add_heading("Hipóteses fracas", level=2)
    weak = [h for h in review.hypotheses.all() if h.hypothesis_type == "W"]
    if weak:
        for i, h in enumerate(weak, 1):
            doc.add_paragraph(f"HW{i}: {h.statement}", style="List Number")
    else:
        doc.add_paragraph("—")

    doc.add_heading("Keywords", level=1)
    for kw in review.keywords.all():
        label = f"[{kw.related_to}] {kw.description}"
        if kw.synonym_of_id:
            label += " (synonym)"
        doc.add_paragraph(label, style="List Bullet")

    doc.add_heading("String de busca geral", level=1)
    base = review.get_base_search_session()
    doc.add_paragraph(base.search_string if base else "—")

    doc.add_heading("Strings de busca por base de dados", level=1)
    for source in review.sources.order_by("name"):
        sess = review.get_source_search_session(source.id)
        doc.add_heading(source.name, level=2)
        doc.add_paragraph(sess.search_string if sess and sess.search_string else "—")

    doc.add_heading("Bases de dados utilizadas", level=1)
    for source in review.sources.all():
        doc.add_paragraph(source.name, style="List Bullet")

    doc.add_heading("Inclusion Criteria", level=1)
    for c in review.selection_criteria.all():
        if c.criteria_type == "I":
            doc.add_paragraph(c.description, style="List Bullet")

    doc.add_heading("Exclusion Criteria", level=1)
    for c in review.selection_criteria.all():
        if c.criteria_type == "E":
            doc.add_paragraph(c.description, style="List Bullet")

    doc.add_heading("Quality Assessment Checklist", level=1)
    for q in review.quality_questions.all():
        doc.add_paragraph(q.description, style="List Number")

    doc.add_heading("Data Extraction Form", level=1)
    for field in review.extraction_fields.all():
        doc.add_paragraph(f"{field.description} ({field.FIELD_TYPES.get(field.field_type, field.field_type)})")

    output = BytesIO()
    doc.save(output)
    output.seek(0)
    return output


def _docx_add_paragraphs(doc, text: str):
    for block in (text or "").split("\n\n"):
        block = block.strip()
        if block:
            doc.add_paragraph(block)


def _docx_add_table(doc, headers: list[str], rows: list[list]):
    if not rows:
        doc.add_paragraph("(sem dados)")
        return
    table = doc.add_table(rows=1 + len(rows), cols=len(headers))
    table.style = "Table Grid"
    hdr = table.rows[0].cells
    for i, h in enumerate(headers):
        hdr[i].text = str(h)
    for ri, row in enumerate(rows):
        for ci, val in enumerate(row):
            table.rows[ri + 1].cells[ci].text = str(val)


_SYNTHESIS_DOCX_SECTIONS = (
    ("resumo_executivo", "Resumo executivo"),
    ("conformidade_prisma", "Conformidade com PRISMA 2020"),
    ("introducao", "Introdução"),
    ("metodos", "Metodologia"),
    ("resultados_selecao", "Resultados do processo de seleção"),
    ("caracterizacao", "Caracterização dos estudos incluídos"),
    ("sintese_tematica", "Síntese temática e cruzamentos"),
    ("lacunas_literatura", "Lacunas reportadas na literatura"),
    ("discussao", "Discussão"),
    ("lacunas_rsl", "Limitações da revisão"),
    ("qualidade_metodologica_rsl", "Qualidade metodológica da RSL"),
    ("conclusao", "Conclusão"),
)


def export_extraction_matrix_csv(review, package: dict) -> BytesIO:
    """Matriz completa estudo × campo (seleção final) para auditoria."""
    output = StringIO()
    fields = list(review.extraction_fields.all())
    headers = ["article_id", "title", "authors", "year", "source", "doi", "qa_score", "extraction_finished"]
    headers += [f.description for f in fields]
    writer = csv.writer(output)
    writer.writerow(headers)
    for row in package.get("dataset", []):
        line = [
            row["id"],
            row["title"],
            row["authors"],
            row["year"],
            row["source"],
            row["doi"],
            row.get("qa_score", ""),
            row.get("finished", ""),
        ]
        for f in fields:
            val = row.get("fields", {}).get(f.description, "")
            if isinstance(val, list):
                val = "; ".join(val)
            line.append(val)
        writer.writerow(line)
    data = BytesIO(output.getvalue().encode("utf-8-sig"))
    data.seek(0)
    return data


def export_synthesis_docx(review, package: dict) -> BytesIO:
    """Relatório discursivo da RSL com tabelas (~10+ páginas)."""
    doc = Document()
    meta = package["meta"]
    funnel = package["funnel"]
    sections = package.get("sections", {})

    doc.add_heading(review.title, 0)
    doc.add_paragraph(
        f"Síntese da revisão sistemática — gerado em {meta.get('generated_at', '')}. "
        f"Estimativa: {meta.get('estimated_pages', 10)}+ páginas. "
        f"Revisão ID {review.id}; seleção final n={meta.get('n_final', 0)}."
    )

    sec_num = 0
    for key, title in _SYNTHESIS_DOCX_SECTIONS:
        sec_num += 1
        doc.add_heading(f"{sec_num}. {title}", level=1)
        _docx_add_paragraphs(doc, sections.get(key, ""))

        if key == "conformidade_prisma":
            _docx_add_table(
                doc,
                ["Etapa PRISMA", "Contagem"],
                [
                    ["Identificados", funnel["identificados"]],
                    ["Aceitos", funnel["aceitos"]],
                    ["Rejeitados", funnel["rejeitados"]],
                    ["Duplicados", funnel["duplicados"]],
                    ["Seleção final", funnel["selecao_final"]],
                ],
            )

        if key == "resultados_selecao" and package.get("year_distribution"):
            doc.add_heading("Distribuição por ano", level=2)
            _docx_add_table(
                doc,
                ["Ano", "n", "%"],
                [
                    [r["year"], r["count"], f"{r['percent']}%"]
                    for r in package["year_distribution"]
                ],
            )
            doc.add_heading("Distribuição por base", level=2)
            _docx_add_table(
                doc,
                ["Base", "n", "%"],
                [
                    [r["source"], r["count"], f"{r['percent']}%"]
                    for r in package.get("source_distribution", [])
                ],
            )

    doc.add_heading("Anexos — Tabelas de síntese", level=1)
    doc.add_heading("Tabela 1 — Matriz de caracterização", level=2)
    _docx_add_table(
        doc,
        ["Autores", "Ano", "Base", "Domínio", "Modelo", "Edge", "LLM", "QA"],
        [
            [
                r["authors"],
                r["year"],
                r["source"],
                r["domain"],
                r["model"],
                r["edge"],
                r["llm"],
                r["qa"] if r["qa"] is not None else "—",
            ]
            for r in package.get("summary_table", [])
        ],
    )

    for ft in package.get("frequency_tables", []):
        doc.add_heading(f"Tabela de frequência — {ft['field']}", level=2)
        _docx_add_table(
            doc,
            ["Categoria", "N", "%"],
            [[r["label"], r["count"], f"{r['percent']}%"] for r in ft.get("rows", [])],
        )

    for ct in package.get("crosstabs", []):
        doc.add_heading(f"Cruzamento: {ct['field_a']} × {ct['field_b']}", level=2)
        headers = [ct["field_a"]] + list(ct["columns"]) + ["Total linha"]
        rows = []
        for row in ct.get("rows", []):
            line = [row["row_label"]]
            for cell in row["cells"]:
                line.append(str(cell["count"]))
            line.append(str(row["row_total"]))
            rows.append(line)
        _docx_add_table(doc, headers, rows)

    doc.add_heading("Tabela — Temas de Research Gap", level=2)
    _docx_add_table(
        doc,
        ["Tema (resumo)", "Estudos", "Referências"],
        [
            [g["theme"], g["count"], ", ".join(g["studies"][:5])]
            for g in package.get("gaps", [])[:25]
        ],
    )

    if package.get("rq_coverage"):
        doc.add_heading("Tabela — Cobertura por pergunta de pesquisa (RQ)", level=2)
        _docx_add_table(
            doc,
            ["RQ", "Pergunta", "Estudos (heurística)", "Observação"],
            [
                [rq.get("rq_id", ""), rq["question"][:100], rq["n_studies"], rq["note"][:80]]
                for rq in package["rq_coverage"]
            ],
        )

    if package.get("hypothesis_evidence"):
        doc.add_heading("Tabela — Evidências por hipótese", level=2)
        _docx_add_table(
            doc,
            ["ID", "Tipo", "Hipótese", "N estudos", "Nota"],
            [
                [
                    h["hypothesis_id"],
                    h["type_label"],
                    h["statement"][:100],
                    h["n_studies"],
                    h["synthesis_note"][:80],
                ]
                for h in package["hypothesis_evidence"]
            ],
        )

    doc.add_heading("Tabela — Completude da extração", level=2)
    _docx_add_table(
        doc,
        ["Campo", "Preenchidos", "Total", "%"],
        [
            [r["field"], r["filled"], r["total"], f"{r['percent']}%"]
            for r in package.get("fill_rates", [])
        ],
    )

    if package.get("limitations"):
        doc.add_heading("Apêndice — Limitações por estudo (amostra)", level=1)
        for lim in package["limitations"][:15]:
            doc.add_paragraph(f"{lim['study']} {lim['ref']}")
            doc.add_paragraph(lim["text"][:800])

    output = BytesIO()
    doc.save(output)
    output.seek(0)
    return output


def export_synthesis_xlsx(review, package: dict) -> BytesIO:
    wb = Workbook()
    ws = wb.active
    ws.title = "Caracterização"
    ws.append(["ID", "Autores", "Ano", "Base", "Domínio", "Modelo", "Edge", "LLM", "QA"])
    for r in package.get("summary_table", []):
        ws.append(
            [
                r["id"],
                r["authors"],
                r["year"],
                r["source"],
                r["domain"],
                r["model"],
                r["edge"],
                r["llm"],
                r["qa"],
            ]
        )

    for ft in package.get("frequency_tables", []):
        name = (ft["field"][:28] if ft["field"] else "Freq")[:31]
        sh = wb.create_sheet(title=name)
        sh.append(["Categoria", "N", "%"])
        for row in ft.get("rows", []):
            sh.append([row["label"], row["count"], row["percent"]])

    sh = wb.create_sheet(title="Lacunas")
    sh.append(["Tema", "N estudos", "Referências"])
    for g in package.get("gaps", []):
        sh.append([g["theme"], g["count"], ", ".join(g["studies"])])

    sh = wb.create_sheet(title="Funil")
    sh.append(["Etapa", "N"])
    f = package["funnel"]
    for label, key in [
        ("Identificados", "identificados"),
        ("Aceitos", "aceitos"),
        ("Rejeitados", "rejeitados"),
        ("Duplicados", "duplicados"),
        ("Seleção final", "selecao_final"),
    ]:
        sh.append([label, f[key]])

    if package.get("year_distribution"):
        sh = wb.create_sheet(title="Por ano")
        sh.append(["Ano", "N", "%"])
        for r in package["year_distribution"]:
            sh.append([r["year"], r["count"], r["percent"]])

    if package.get("source_distribution"):
        sh = wb.create_sheet(title="Por base")
        sh.append(["Base", "N", "%"])
        for r in package["source_distribution"]:
            sh.append([r["source"], r["count"], r["percent"]])

    output = BytesIO()
    wb.save(output)
    output.seek(0)
    return output
