"""Tests for read_pdf skill.

QA conditions satisfied:
  1. At least one test uses a local fixture PDF (no network required) — see test_local_fixture_*
  2. Network-dependent URL tests are guarded by FLIIQ_TEST_NETWORK env var
  3. Content-Type validation error path is tested with mocked httpx
  4. Encrypted PDF detection is tested
  5. Page range selection is tested
"""

from __future__ import annotations

import os
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

# ---------------------------------------------------------------------------
# Fixture: minimal valid PDF (no external files needed)
# ---------------------------------------------------------------------------

def _make_minimal_pdf(text: str = "Hello, Fliiq PDF world!") -> bytes:
    """Generate a minimal valid PDF with one page of text.

    This is a hand-crafted PDF that PyMuPDF can parse — no dependencies.
    """
    # Minimal PDF structure
    content = f"BT /F1 12 Tf 72 720 Td ({text}) Tj ET"
    content_bytes = content.encode()

    objects = []

    # Object 1: Catalog
    objects.append(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n")

    # Object 2: Pages
    objects.append(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n")

    # Object 3: Page
    objects.append(
        b"3 0 obj\n"
        b"<< /Type /Page /Parent 2 0 R "
        b"/MediaBox [0 0 612 792] "
        b"/Contents 4 0 R "
        b"/Resources << /Font << /F1 5 0 R >> >> >>\n"
        b"endobj\n"
    )

    # Object 4: Content stream
    stream_data = content_bytes
    objects.append(
        b"4 0 obj\n"
        b"<< /Length " + str(len(stream_data)).encode() + b" >>\n"
        b"stream\n" + stream_data + b"\nendstream\nendobj\n"
    )

    # Object 5: Font
    objects.append(
        b"5 0 obj\n"
        b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\n"
        b"endobj\n"
    )

    # Build PDF
    header = b"%PDF-1.4\n"
    body = b""
    offsets = []
    pos = len(header)
    for obj in objects:
        offsets.append(pos)
        body += obj
        pos += len(obj)

    # Cross-reference table
    xref_pos = len(header) + len(body)
    xref = b"xref\n"
    xref += f"0 {len(objects) + 1}\n".encode()
    xref += b"0000000000 65535 f \n"
    for off in offsets:
        xref += f"{off:010d} 00000 n \n".encode()

    trailer = (
        b"trailer\n"
        b"<< /Size " + str(len(objects) + 1).encode() + b" /Root 1 0 R >>\n"
        b"startxref\n" + str(xref_pos).encode() + b"\n%%EOF\n"
    )

    return header + body + xref + trailer


@pytest.fixture
def sample_pdf_path(tmp_path: Path) -> Path:
    """Write a minimal PDF to a temp file and return its path."""
    pdf_path = tmp_path / "sample.pdf"
    pdf_path.write_bytes(_make_minimal_pdf("Test content for read_pdf skill"))
    return pdf_path


@pytest.fixture
def multipage_pdf_path(tmp_path: Path) -> Path:
    """Multi-page PDF for page selection tests."""
    # We'll use PyMuPDF to create a proper multi-page PDF if available,
    # otherwise fall back to single page
    try:
        import fitz
        doc = fitz.open()
        for i in range(1, 4):
            page = doc.new_page()
            page.insert_text((72, 720), f"Page {i} content: test text for page {i}")
        pdf_bytes = doc.tobytes()
        doc.close()
    except ImportError:
        pdf_bytes = _make_minimal_pdf("Single page fallback")

    pdf_path = tmp_path / "multipage.pdf"
    pdf_path.write_bytes(pdf_bytes)
    return pdf_path


# ---------------------------------------------------------------------------
# Local file tests (no network — always run)
# ---------------------------------------------------------------------------

def test_local_pdf_basic(sample_pdf_path):
    """Read a local PDF — returns expected fields."""
    from main import read_pdf

    result = read_pdf(path=str(sample_pdf_path))

    assert "error" not in result, f"Unexpected error: {result.get('error')}"
    assert result["page_count"] >= 1
    assert "title" in result
    assert "author" in result
    assert "full_text" in result
    assert "pages" in result
    assert isinstance(result["pages"], list)
    assert result["source"] == str(sample_pdf_path)


def test_local_pdf_structured_mode(sample_pdf_path):
    """Structured mode returns per-page dicts."""
    from main import read_pdf

    result = read_pdf(path=str(sample_pdf_path), mode="structured")

    assert "error" not in result
    assert isinstance(result["pages"], list)
    for page in result["pages"]:
        assert "page_num" in page
        assert "text" in page
        assert isinstance(page["page_num"], int)
        assert page["page_num"] >= 1


def test_local_pdf_full_text_mode(sample_pdf_path):
    """Full_text mode returns concatenated string, pages list is empty."""
    from main import read_pdf

    result = read_pdf(path=str(sample_pdf_path), mode="full_text")

    assert "error" not in result
    assert isinstance(result["full_text"], str)
    assert result["pages"] == []


def test_local_pdf_page_selection(multipage_pdf_path):
    """Page selection — only requested pages are returned."""
    from main import read_pdf

    result = read_pdf(path=str(multipage_pdf_path), pages=[1])

    assert "error" not in result
    assert result["pages_extracted"] == 1
    if result["pages"]:  # structured mode
        assert result["pages"][0]["page_num"] == 1


def test_file_not_found():
    """Non-existent file returns error dict, not exception."""
    from main import read_pdf

    result = read_pdf(path="/nonexistent/path/to/file.pdf")

    assert "error" in result
    assert "not found" in result["error"].lower()
    assert result["page_count"] == 0


def test_missing_both_params():
    """Neither path nor url → error."""
    from main import read_pdf

    result = read_pdf()

    assert "error" in result
    assert result["page_count"] == 0


def test_both_params_provided(sample_pdf_path):
    """Both path and url → error."""
    from main import read_pdf

    result = read_pdf(path=str(sample_pdf_path), url="https://example.com/file.pdf")

    assert "error" in result
    assert result["page_count"] == 0


# ---------------------------------------------------------------------------
# Content-Type validation (mocked — no network)
# ---------------------------------------------------------------------------

def test_url_wrong_content_type_html():
    """URL returning HTML Content-Type → clear error, not cryptic exception."""
    from main import read_pdf

    mock_response = MagicMock()
    mock_response.headers = {"content-type": "text/html; charset=utf-8", "content-length": "1000"}
    mock_response.raise_for_status = MagicMock()
    mock_response.content = b"<html><body>Not a PDF</body></html>"

    mock_head_response = MagicMock()
    mock_head_response.headers = {"content-type": "text/html; charset=utf-8", "content-length": "1000"}

    mock_client = MagicMock()
    mock_client.__enter__ = MagicMock(return_value=mock_client)
    mock_client.__exit__ = MagicMock(return_value=False)
    mock_client.head = MagicMock(return_value=mock_head_response)
    mock_client.get = MagicMock(return_value=mock_response)

    with patch("httpx.Client", return_value=mock_client):
        result = read_pdf(url="https://example.com/not-a-pdf")

    assert "error" in result
    assert "pdf" in result["error"].lower() or "content-type" in result["error"].lower()


def test_url_size_limit_exceeded():
    """URL with Content-Length exceeding limit → size error."""
    from main import read_pdf

    mock_head_response = MagicMock()
    # 100 MB — exceeds default 50 MB limit
    mock_head_response.headers = {
        "content-type": "application/pdf",
        "content-length": str(100 * 1024 * 1024),
    }

    mock_client = MagicMock()
    mock_client.__enter__ = MagicMock(return_value=mock_client)
    mock_client.__exit__ = MagicMock(return_value=False)
    mock_client.head = MagicMock(return_value=mock_head_response)

    with patch("httpx.Client", return_value=mock_client):
        result = read_pdf(url="https://example.com/huge.pdf")

    assert "error" in result
    assert "large" in result["error"].lower() or "limit" in result["error"].lower()


# ---------------------------------------------------------------------------
# Encrypted PDF test (mocked via PyMuPDF)
# ---------------------------------------------------------------------------

def test_encrypted_pdf_clear_error(sample_pdf_path):
    """Encrypted PDF returns a clear error message."""
    from main import read_pdf

    # Mock the fitz.open to return an encrypted document
    mock_doc = MagicMock()
    mock_doc.is_encrypted = True
    mock_doc.close = MagicMock()

    with patch("fitz.open", return_value=mock_doc):
        result = read_pdf(path=str(sample_pdf_path))

    assert "error" in result
    assert "encrypt" in result["error"].lower() or "password" in result["error"].lower()
    assert result["page_count"] == 0


# ---------------------------------------------------------------------------
# Network tests (guarded by FLIIQ_TEST_NETWORK env var)
# ---------------------------------------------------------------------------

@pytest.mark.skipif(
    os.environ.get("FLIIQ_TEST_NETWORK", "0") != "1",
    reason="Network tests disabled. Set FLIIQ_TEST_NETWORK=1 to enable."
)
def test_url_pdf_fetch_network():
    """Fetch a small PDF from a public URL — requires network."""
    from main import read_pdf

    # Use a small, stable arXiv PDF
    result = read_pdf(url="https://arxiv.org/pdf/1706.03762")

    assert "error" not in result, f"Unexpected error: {result.get('error')}"
    assert result["page_count"] > 0
    assert len(result["full_text"]) > 100
