"""Tests for /api/v1/export/docx.

Gerçek DOCX üretilir (mock yok) ve ZIP içeriği + TürkPatent format kuralları
doğrulanır: margins, line numbering, page number field, 3 ayrı dosya.
"""

from __future__ import annotations

import io
import zipfile
from xml.etree import ElementTree as ET

import pytest
from docx import Document
from docx.shared import Mm
from httpx import ASGITransport, AsyncClient

from app.main import app

W_NS = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"


def _sample_payload() -> dict:
    return {
        "title": "Post-kuantum TLS ağ geçidi",
        "reference_code": "P-2026-042",
        "description_markdown": (
            "## Buluşun Ait Olduğu Teknik Alan\n\n"
            "Bu buluş, veri haberleşme güvenliği alanına ilişkin olup, özellikle "
            "post-kuantum kriptografi tabanlı ağ geçitleri ile ilgilidir.\n\n"
            "## Buluşun Ayrıntılı Açıklaması\n\n"
            "Ağ geçidi (100), bir bağlantı yöneticisi (210) ve sertifika doğrulayıcı "
            "(220) içerir. Tek TLS handshake içinde ML-DSA imzalı X25519 anahtar "
            "paylaşımı gerçekleştirir."
        ),
        "claims": [
            {
                "number": 1,
                "type": "independent",
                "parent": None,
                "text": "Bir post-kuantum TLS ağ geçidi olup, özelliği; ML-DSA modülü (230) içermesidir.",
            },
            {
                "number": 2,
                "type": "dependent",
                "parent": 1,
                "text": "İstem 1'e göre ağ geçidi, karakterize edici özelliği; X25519 paylaşımı yapmasıdır.",
            },
        ],
        "abstract": (
            "Bu buluş, kuantum bilgisayarlara karşı dayanıklı bir TLS ağ geçidi sağlar. "
            "ML-DSA imzalı X25519 anahtar paylaşımı tek handshake içinde yapılır."
        ),
    }


@pytest.mark.asyncio
async def test_export_docx_returns_zip_with_three_files() -> None:
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/v1/export/docx", json=_sample_payload())

    assert response.status_code == 200
    assert response.headers["content-type"] == "application/zip"
    assert "attachment" in response.headers["content-disposition"]
    assert "P-2026-042" in response.headers["content-disposition"]

    zf = zipfile.ZipFile(io.BytesIO(response.content))
    names = sorted(zf.namelist())
    assert len(names) == 3

    # Üç ayrı dosya: Tarifname, Istemler, Ozet (EPATS kuralı)
    assert any("Tarifname" in n for n in names)
    assert any("Istemler" in n for n in names)
    assert any("Ozet" in n for n in names)


@pytest.mark.asyncio
async def test_exported_docx_has_turkpatent_margins() -> None:
    """Kritik: EPATS rejects non-compliant margins. 20/25/20/20 mm şart."""
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/v1/export/docx", json=_sample_payload())

    zf = zipfile.ZipFile(io.BytesIO(response.content))
    tarifname_name = next(n for n in zf.namelist() if "Tarifname" in n)
    tarifname_bytes = zf.read(tarifname_name)

    doc = Document(io.BytesIO(tarifname_bytes))
    section = doc.sections[0]

    # python-docx Mm() bazen 1-200 EMU yuvarlama farkı yapar; ~0.006mm tolerans.
    def close(a, b, tol_emu=1000):
        return abs(int(a) - int(b)) <= tol_emu

    assert close(section.top_margin, Mm(20))
    assert close(section.left_margin, Mm(25))
    assert close(section.right_margin, Mm(20))
    assert close(section.bottom_margin, Mm(20))
    assert close(section.page_width, Mm(210))
    assert close(section.page_height, Mm(297))


@pytest.mark.asyncio
async def test_exported_docx_has_line_numbering_xml() -> None:
    """w:lnNumType countBy=5, restart=newPage — XML seviyesinde doğrula."""
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/v1/export/docx", json=_sample_payload())

    zf = zipfile.ZipFile(io.BytesIO(response.content))
    tarifname_name = next(n for n in zf.namelist() if "Tarifname" in n)
    docx_bytes = zf.read(tarifname_name)

    # DOCX bir zip dosyası; içinde word/document.xml var
    inner_zf = zipfile.ZipFile(io.BytesIO(docx_bytes))
    document_xml = inner_zf.read("word/document.xml")

    tree = ET.fromstring(document_xml)
    ln_num_elements = tree.findall(f".//{W_NS}lnNumType")
    assert len(ln_num_elements) == 1, "Tek bir satır numaralama elemanı olmalı"

    attrs = ln_num_elements[0].attrib
    assert attrs.get(f"{W_NS}countBy") == "5"
    assert attrs.get(f"{W_NS}restart") == "newPage"


@pytest.mark.asyncio
async def test_exported_docx_has_page_number_field() -> None:
    """Footer'da Word PAGE field olmalı (alt ortalanmış sayfa numarası)."""
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/v1/export/docx", json=_sample_payload())

    zf = zipfile.ZipFile(io.BytesIO(response.content))
    tarifname_name = next(n for n in zf.namelist() if "Tarifname" in n)
    docx_bytes = zf.read(tarifname_name)
    inner_zf = zipfile.ZipFile(io.BytesIO(docx_bytes))

    # Footer XML dosyaları footer1.xml, footer2.xml... içinde
    footer_names = [n for n in inner_zf.namelist() if "footer" in n.lower()]
    assert footer_names, "Footer XML bulunmalı (PAGE field için)"

    all_footer_xml = b"".join(inner_zf.read(n) for n in footer_names)
    assert b"PAGE" in all_footer_xml
    assert b"fldChar" in all_footer_xml


@pytest.mark.asyncio
async def test_exported_claims_contain_numbered_text() -> None:
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/v1/export/docx", json=_sample_payload())

    zf = zipfile.ZipFile(io.BytesIO(response.content))
    istemler_name = next(n for n in zf.namelist() if "Istemler" in n)
    doc = Document(io.BytesIO(zf.read(istemler_name)))

    text_parts = [p.text for p in doc.paragraphs]
    all_text = "\n".join(text_parts)

    assert "İSTEMLER" in all_text
    assert "1." in all_text  # istem 1 başlangıcı
    assert "2." in all_text  # istem 2 başlangıcı
    assert "ML-DSA" in all_text


@pytest.mark.asyncio
async def test_export_rejects_empty_claims() -> None:
    payload = _sample_payload()
    payload["claims"] = []
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/v1/export/docx", json=payload)
    assert response.status_code == 422


# ------------------- Figures (Resimler.docx) -------------------


def _tiny_png_base64() -> str:
    """Minimal 1x1 beyaz PNG (smoke test için)."""
    import base64

    # 1x1 white PNG (67 bytes)
    png = bytes(
        [
            0x89,
            0x50,
            0x4E,
            0x47,
            0x0D,
            0x0A,
            0x1A,
            0x0A,
            0x00,
            0x00,
            0x00,
            0x0D,
            0x49,
            0x48,
            0x44,
            0x52,
            0x00,
            0x00,
            0x00,
            0x01,
            0x00,
            0x00,
            0x00,
            0x01,
            0x08,
            0x02,
            0x00,
            0x00,
            0x00,
            0x90,
            0x77,
            0x53,
            0xDE,
            0x00,
            0x00,
            0x00,
            0x0C,
            0x49,
            0x44,
            0x41,
            0x54,
            0x08,
            0x99,
            0x63,
            0xF8,
            0xFF,
            0xFF,
            0x3F,
            0x00,
            0x05,
            0xFE,
            0x02,
            0xFE,
            0xDC,
            0xCC,
            0x59,
            0xE7,
            0x00,
            0x00,
            0x00,
            0x00,
            0x49,
            0x45,
            0x4E,
            0x44,
            0xAE,
            0x42,
            0x60,
            0x82,
        ]
    )
    return base64.b64encode(png).decode("ascii")


@pytest.mark.asyncio
async def test_export_adds_figures_docx_when_figures_provided() -> None:
    payload = _sample_payload()
    payload["figures"] = [
        {"png_base64": _tiny_png_base64(), "title": "Sistem mimarisi"},
        {"png_base64": _tiny_png_base64(), "title": "Akış diyagramı"},
    ]

    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/v1/export/docx", json=payload)

    assert response.status_code == 200
    zf = zipfile.ZipFile(io.BytesIO(response.content))
    names = zf.namelist()
    assert len(names) == 4
    assert any("Resimler" in n for n in names)


@pytest.mark.asyncio
async def test_figures_docx_has_correct_margins_and_no_line_numbering() -> None:
    """Resim sayfası marjları 25/25/15/10, satır numarası YOK."""
    payload = _sample_payload()
    payload["figures"] = [{"png_base64": _tiny_png_base64(), "title": "Test"}]

    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/v1/export/docx", json=payload)

    zf = zipfile.ZipFile(io.BytesIO(response.content))
    resimler_name = next(n for n in zf.namelist() if "Resimler" in n)
    docx_bytes = zf.read(resimler_name)

    doc = Document(io.BytesIO(docx_bytes))
    section = doc.sections[0]

    def close(a, b, tol_emu=1000):
        return abs(int(a) - int(b)) <= tol_emu

    # TPMK resim sayfası: 25/25/15/10 mm
    assert close(section.top_margin, Mm(25))
    assert close(section.left_margin, Mm(25))
    assert close(section.right_margin, Mm(15))
    assert close(section.bottom_margin, Mm(10))

    # Satır numarası YOK — word/document.xml'de lnNumType bulunmamalı
    inner_zf = zipfile.ZipFile(io.BytesIO(docx_bytes))
    doc_xml = inner_zf.read("word/document.xml")
    tree = ET.fromstring(doc_xml)
    assert tree.findall(f".//{W_NS}lnNumType") == [], "Resim sayfasında satır numarası olmamalı"


@pytest.mark.asyncio
async def test_figures_docx_has_page_number_n_of_total() -> None:
    """Footer'da PAGE field + '/N' literal sabit — 'N/toplam' formatı."""
    payload = _sample_payload()
    payload["figures"] = [
        {"png_base64": _tiny_png_base64(), "title": None},
        {"png_base64": _tiny_png_base64(), "title": None},
        {"png_base64": _tiny_png_base64(), "title": None},
    ]

    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/v1/export/docx", json=payload)

    zf = zipfile.ZipFile(io.BytesIO(response.content))
    resimler_name = next(n for n in zf.namelist() if "Resimler" in n)
    docx_bytes = zf.read(resimler_name)
    inner_zf = zipfile.ZipFile(io.BytesIO(docx_bytes))

    footer_names = [n for n in inner_zf.namelist() if "footer" in n.lower()]
    assert footer_names
    all_footer_xml = b"".join(inner_zf.read(n) for n in footer_names)
    assert b"PAGE" in all_footer_xml
    assert b"/3" in all_footer_xml  # toplam şekil sayısı


@pytest.mark.asyncio
async def test_export_rejects_invalid_png_figure() -> None:
    import base64 as b64mod

    # 50+ byte string that decodes cleanly but isn't a PNG
    fake_bytes = b"NOT A PNG FILE " + (b"X" * 100)
    fake_b64 = b64mod.b64encode(fake_bytes).decode("ascii")

    payload = _sample_payload()
    payload["figures"] = [{"png_base64": fake_b64, "title": "Fake"}]
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/v1/export/docx", json=payload)
    assert response.status_code == 400
    assert "magic bytes" in response.json()["detail"].lower()
