"""Tests for the headless browser PDF fetching module."""

import asyncio
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from huntpdf.browser import fetch_pdf_browser, fetch_pdf_browser_sync
from huntpdf.errors import DownloadFailed, PDFNotFound


def _run(coro):
    return asyncio.run(coro)


def _make_pw_mocks(*, page_side_effect=None):
    """Build a full set of Playwright mocks and return (mock_pw_func, mock_page)."""
    mock_page = AsyncMock()
    mock_page.on = MagicMock()

    if page_side_effect:
        mock_page.goto.side_effect = page_side_effect

    mock_browser = AsyncMock()
    mock_browser.new_page.return_value = mock_page

    mock_pw = AsyncMock()
    mock_pw.chromium.launch.return_value = mock_browser

    mock_context_manager = AsyncMock()
    mock_context_manager.__aenter__.return_value = mock_pw
    mock_context_manager.__aexit__.return_value = False

    mock_pw_func = MagicMock(return_value=mock_context_manager)
    return mock_pw_func, mock_page, mock_pw


class TestPDFInterception:
    """Tests for successful PDF interception via mocked Playwright."""

    def test_intercepts_pdf_response(self, tmp_path):
        pdf_bytes = b"%PDF-1.4 fake content"
        output = tmp_path / "paper.pdf"

        mock_response = AsyncMock()
        mock_response.headers = {"content-type": "application/pdf"}
        mock_response.body = AsyncMock(return_value=pdf_bytes)

        mock_pw_func, mock_page, _ = _make_pw_mocks()

        # Capture the response handler registered via page.on("response", handler)
        captured_handler = None
        original_on = mock_page.on

        def capture_on(event, handler):
            nonlocal captured_handler
            if event == "response":
                captured_handler = handler

        mock_page.on = capture_on

        # When goto is called, fire the captured handler with the mock PDF response
        async def fake_goto(*args, **kwargs):
            if captured_handler:
                await captured_handler(mock_response)

        mock_page.goto = fake_goto

        with patch("huntpdf.browser.async_playwright", mock_pw_func):
            result = _run(fetch_pdf_browser("https://example.com/paper.pdf", output))

        assert result == output
        assert output.read_bytes() == pdf_bytes

    def test_sync_wrapper(self, tmp_path):
        pdf_bytes = b"%PDF-1.4 sync test"
        output = tmp_path / "sync.pdf"

        mock_response = AsyncMock()
        mock_response.headers = {"content-type": "application/pdf"}
        mock_response.body = AsyncMock(return_value=pdf_bytes)

        mock_pw_func, mock_page, _ = _make_pw_mocks()

        captured_handler = None

        def capture_on(event, handler):
            nonlocal captured_handler
            if event == "response":
                captured_handler = handler

        mock_page.on = capture_on

        async def fake_goto(*args, **kwargs):
            if captured_handler:
                await captured_handler(mock_response)

        mock_page.goto = fake_goto

        with patch("huntpdf.browser.async_playwright", mock_pw_func):
            result = fetch_pdf_browser_sync("https://example.com/paper.pdf", output)

        assert result == output
        assert output.read_bytes() == pdf_bytes

    def test_creates_parent_directories(self, tmp_path):
        pdf_bytes = b"%PDF-1.4 nested"
        output = tmp_path / "deep" / "nested" / "paper.pdf"

        mock_response = AsyncMock()
        mock_response.headers = {"content-type": "application/pdf"}
        mock_response.body = AsyncMock(return_value=pdf_bytes)

        mock_pw_func, mock_page, _ = _make_pw_mocks()

        captured_handler = None

        def capture_on(event, handler):
            nonlocal captured_handler
            if event == "response":
                captured_handler = handler

        mock_page.on = capture_on

        async def fake_goto(*args, **kwargs):
            if captured_handler:
                await captured_handler(mock_response)

        mock_page.goto = fake_goto

        with patch("huntpdf.browser.async_playwright", mock_pw_func):
            result = _run(fetch_pdf_browser("https://example.com/paper.pdf", output))

        assert result == output
        assert output.read_bytes() == pdf_bytes


class TestNoPDFFound:
    """Tests for when no PDF is intercepted."""

    def test_raises_pdf_not_found_when_no_pdf_response(self, tmp_path):
        output = tmp_path / "missing.pdf"
        mock_pw_func, _, _ = _make_pw_mocks()

        with patch("huntpdf.browser.async_playwright", mock_pw_func):
            with pytest.raises(PDFNotFound):
                _run(fetch_pdf_browser("https://example.com/nofile", output))


class TestPlaywrightNotInstalled:
    """Tests for missing Playwright installation."""

    def test_import_error_gives_helpful_message(self, tmp_path):
        output = tmp_path / "fail.pdf"

        with patch("huntpdf.browser.async_playwright", None):
            with pytest.raises(DownloadFailed, match="playwright install chromium"):
                _run(fetch_pdf_browser("https://example.com", output))

    def test_browser_executable_missing(self, tmp_path):
        output = tmp_path / "fail.pdf"

        mock_pw = AsyncMock()
        mock_pw.chromium.launch.side_effect = Exception(
            "Executable doesn't exist at /path/to/chromium"
        )

        mock_context_manager = AsyncMock()
        mock_context_manager.__aenter__.return_value = mock_pw
        mock_context_manager.__aexit__.return_value = False

        mock_pw_func = MagicMock(return_value=mock_context_manager)

        with patch("huntpdf.browser.async_playwright", mock_pw_func):
            with pytest.raises(DownloadFailed, match="playwright install chromium"):
                _run(fetch_pdf_browser("https://example.com", output))


@pytest.mark.integration
class TestBrowserIntegration:
    """Integration tests requiring real Playwright installation."""

    def test_real_pdf_download(self, tmp_path):
        pytest.skip("Requires playwright install chromium")
