"""Tests for /api/v1/export/pdf ve /export/both.

- Unit: convert_docx_to_pdf mock'lanır, endpoint ZIP'i doğru paketliyor mu?
- Unit: LibreOffice yok → 503
- Integration: gerçek soffice varsa küçük DOCX → PDF çevirimi
"""

from __future__ import annotations

import io
import zipfile
from unittest.mock import AsyncMock, patch

import pytest
from httpx import ASGITransport, AsyncClient

from app.main import app
from app.services.docx import LibreOfficeNotFoundError, soffice_available


def _payload() -> dict:
    return {
        "title": "Test Patent",
        "reference_code": "P-PDF-001",
        "description_markdown": (
            "## Buluşun Ait Olduğu Teknik Alan\n\n" + ("Bu buluş, test alanına ilişkindir. " * 20)
        ),
        "claims": [
            {
                "number": 1,
                "type": "independent",
                "parent": None,
                "text": "Bir test sistemi olup, özelliği; test yapmasıdır.",
            },
        ],
        "abstract": "Bu bir test özetidir, yeterince uzun olmalı bu yüzden genişletiyorum.",
    }


# ---------------- Unit tests with mocked converter ----------------


@pytest.mark.asyncio
async def test_pdf_endpoint_returns_zip_of_pdfs_with_mock() -> None:
    fake_pdf = b"%PDF-1.7\nmock pdf content\n%%EOF"

    with patch(
        "app.api.export.convert_docx_to_pdf",
        new=AsyncMock(return_value=fake_pdf),
    ):
        transport = ASGITransport(app=app)
        async with AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post("/api/v1/export/pdf", json=_payload())

    assert response.status_code == 200
    assert response.headers["content-type"] == "application/zip"
    assert "P-PDF-001" in response.headers["content-disposition"]
    assert "_PDF" in response.headers["content-disposition"]

    zf = zipfile.ZipFile(io.BytesIO(response.content))
    names = zf.namelist()
    # Figures yok → 3 PDF
    assert len(names) == 3
    assert all(n.endswith(".pdf") for n in names)
    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)
    # PDF magic bytes
    assert zf.read(names[0]).startswith(b"%PDF")


@pytest.mark.asyncio
async def test_both_endpoint_returns_docx_and_pdf() -> None:
    fake_pdf = b"%PDF-1.7\nmock\n%%EOF"

    with patch(
        "app.api.export.convert_docx_to_pdf",
        new=AsyncMock(return_value=fake_pdf),
    ):
        transport = ASGITransport(app=app)
        async with AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post("/api/v1/export/both", json=_payload())

    assert response.status_code == 200
    assert "_Full" in response.headers["content-disposition"]

    zf = zipfile.ZipFile(io.BytesIO(response.content))
    names = zf.namelist()
    # 3 DOCX + 3 PDF = 6
    assert len(names) == 6
    assert sum(1 for n in names if n.endswith(".docx")) == 3
    assert sum(1 for n in names if n.endswith(".pdf")) == 3


@pytest.mark.asyncio
async def test_pdf_returns_503_when_libreoffice_missing() -> None:
    with patch(
        "app.api.export.convert_docx_to_pdf",
        new=AsyncMock(side_effect=LibreOfficeNotFoundError("soffice yok")),
    ):
        transport = ASGITransport(app=app)
        async with AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post("/api/v1/export/pdf", json=_payload())

    assert response.status_code == 503
    assert "soffice" in response.json()["detail"].lower()


@pytest.mark.asyncio
async def test_pdf_with_figures_includes_resimler() -> None:
    """Figures verilmişse Resimler.docx + Resimler.pdf ikisi de ZIP'te.

    Pillow ile gerçek 10x10 PNG üretiyoruz — python-docx'in resim okuması için
    1x1 yeterli değil (boyut ipucu gerektiriyor).
    """
    import base64 as b64
    from io import BytesIO

    from PIL import Image

    img = Image.new("RGB", (10, 10), color="white")
    buf = BytesIO()
    img.save(buf, format="PNG")
    png_bytes = buf.getvalue()

    payload = _payload()
    payload["figures"] = [{"png_base64": b64.b64encode(png_bytes).decode("ascii"), "title": "Test"}]

    fake_pdf = b"%PDF-1.7\nmock\n%%EOF"
    with patch(
        "app.api.export.convert_docx_to_pdf",
        new=AsyncMock(return_value=fake_pdf),
    ):
        transport = ASGITransport(app=app)
        async with AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post("/api/v1/export/both", json=payload)

    zf = zipfile.ZipFile(io.BytesIO(response.content))
    names = zf.namelist()
    # 4 DOCX + 4 PDF
    assert len(names) == 8
    assert any("Resimler.docx" in n for n in names)
    assert any("Resimler.pdf" in n for n in names)


# ---------------- Integration test (requires real soffice) ----------------


@pytest.mark.asyncio
@pytest.mark.skipif(not soffice_available(), reason="LibreOffice (soffice) kurulu değil")
async def test_real_pdf_conversion_produces_valid_pdf() -> None:
    """Gerçek soffice ile DOCX → PDF — magic bytes + plausible boyut."""
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test", timeout=120.0) as client:
        response = await client.post("/api/v1/export/pdf", json=_payload())

    assert response.status_code == 200, response.text
    zf = zipfile.ZipFile(io.BytesIO(response.content))

    for name in zf.namelist():
        assert name.endswith(".pdf")
        content = zf.read(name)
        assert content.startswith(b"%PDF"), f"{name} PDF magic bytes yok"
        # Gerçek PDF >5 KB (marjinal olanlar bile)
        assert len(content) > 5000, f"{name} çok küçük ({len(content)} bytes)"
