"""
Tests for DownloadService.
"""

from unittest.mock import Mock, MagicMock


class TestDownloadServiceInit:
    """Tests for DownloadService initialization."""

    def test_init_creates_downloaders(self, mocker):
        """Initializes with list of downloaders."""
        # Mock the settings manager
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )

        # Mock Path.mkdir
        mocker.patch("pathlib.Path.mkdir")

        # Mock RetryManager
        mock_retry_manager = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager",
            return_value=mock_retry_manager,
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        assert service.username == "test_user"
        assert len(service.downloaders) > 0
        assert service.library_root == "/tmp/test_library"


class TestDownloadServiceUrlNormalization:
    """Tests for URL normalization."""

    def test_normalize_url_removes_protocol(self, mocker):
        """URL normalization removes http/https protocol."""
        # Create service with minimal initialization
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        url1 = service._normalize_url("https://example.com/path")
        url2 = service._normalize_url("http://example.com/path")

        assert url1 == url2
        assert not url1.startswith("http")

    def test_normalize_url_removes_www(self, mocker):
        """URL normalization removes www prefix."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        url1 = service._normalize_url("https://www.example.com/path")
        url2 = service._normalize_url("https://example.com/path")

        assert url1 == url2
        assert "www" not in url1

    def test_normalize_url_removes_trailing_slash(self, mocker):
        """URL normalization removes trailing slashes."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        url1 = service._normalize_url("https://example.com/path/")
        url2 = service._normalize_url("https://example.com/path")

        assert url1 == url2
        assert not url1.endswith("/")

    def test_normalize_url_sorts_query_params(self, mocker):
        """URL normalization sorts query parameters."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        url1 = service._normalize_url("https://example.com/path?b=2&a=1")
        url2 = service._normalize_url("https://example.com/path?a=1&b=2")

        assert url1 == url2


class TestDownloadServiceIsDownloadable:
    """Tests for _is_downloadable method."""

    def test_is_downloadable_pdf_extension(self, mocker):
        """Identifies .pdf URLs as downloadable."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        mock_resource = Mock()
        mock_resource.url = "https://example.com/paper.pdf"

        assert service._is_downloadable(mock_resource) is True

    def test_is_downloadable_arxiv_url(self, mocker):
        """Identifies arXiv URLs as downloadable."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        mock_resource = Mock()
        mock_resource.url = "https://arxiv.org/abs/2301.00001"

        assert service._is_downloadable(mock_resource) is True

    def test_is_downloadable_pubmed_pmc_url(self, mocker):
        """Identifies PubMed Central URLs as downloadable."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        mock_resource = Mock()
        mock_resource.url = "https://ncbi.nlm.nih.gov/pmc/articles/PMC1234567"

        assert service._is_downloadable(mock_resource) is True

    def test_is_downloadable_biorxiv_url(self, mocker):
        """Identifies bioRxiv URLs as downloadable."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        mock_resource = Mock()
        mock_resource.url = (
            "https://biorxiv.org/content/10.1101/2023.01.01.123456v1"
        )

        assert service._is_downloadable(mock_resource) is True

    def test_is_not_downloadable_regular_html(self, mocker):
        """Rejects regular HTML pages as not downloadable."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        mock_resource = Mock()
        mock_resource.url = "https://example.com/about.html"

        assert service._is_downloadable(mock_resource) is False


class TestDownloadServiceIsAlreadyDownloaded:
    """Tests for is_already_downloaded method."""

    def test_is_already_downloaded_true(self, mocker):
        """Returns True when URL is already downloaded."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        # Mock session and tracker
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        mock_tracker = Mock()
        mock_tracker.is_downloaded = True
        mock_tracker.file_path = "pdfs/test.pdf"

        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_tracker

        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock path exists
        mock_path = Mock()
        mock_path.exists.return_value = True
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_absolute_path_from_settings",
            return_value=mock_path,
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        is_downloaded, file_path = service.is_already_downloaded(
            "https://arxiv.org/pdf/2301.00001.pdf"
        )

        assert is_downloaded is True
        assert file_path is not None

    def test_is_already_downloaded_false_no_tracker(self, mocker):
        """Returns False when no tracker exists."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        # Mock session with no tracker
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_user_db_session",
            return_value=mock_session,
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        is_downloaded, file_path = service.is_already_downloaded(
            "https://arxiv.org/pdf/2301.00001.pdf"
        )

        assert is_downloaded is False
        assert file_path is None


class TestDownloadServiceGetDownloader:
    """Tests for _get_downloader method."""

    def test_get_downloader_for_arxiv(self, mocker):
        """Gets ArxivDownloader for arXiv URLs."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )
        from src.local_deep_research.research_library.downloaders import (
            ArxivDownloader,
        )

        service = DownloadService(username="test_user")

        downloader = service._get_downloader("https://arxiv.org/abs/2301.00001")

        assert downloader is not None
        assert isinstance(downloader, ArxivDownloader)

    def test_get_downloader_for_pubmed(self, mocker):
        """Gets PubMedDownloader for PubMed URLs."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )
        from src.local_deep_research.research_library.downloaders import (
            PubMedDownloader,
        )

        service = DownloadService(username="test_user")

        downloader = service._get_downloader(
            "https://pubmed.ncbi.nlm.nih.gov/12345678"
        )

        assert downloader is not None
        assert isinstance(downloader, PubMedDownloader)

    def test_get_downloader_for_pdf_url(self, mocker):
        """Gets DirectPDFDownloader for direct PDF URLs."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )
        from src.local_deep_research.research_library.downloaders import (
            DirectPDFDownloader,
        )

        service = DownloadService(username="test_user")

        downloader = service._get_downloader("https://example.com/paper.pdf")

        assert downloader is not None
        assert isinstance(downloader, DirectPDFDownloader)


class TestDownloadServiceTextExtraction:
    """Tests for text extraction methods."""

    def test_extract_text_from_pdf_success(self, mocker, mock_pdf_content):
        """Extracts text from valid PDF content."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        # Mock pdfplumber
        mock_pdf = MagicMock()
        mock_page = MagicMock()
        mock_page.extract_text.return_value = "Extracted text from page 1"
        mock_pdf.pages = [mock_page]
        mock_pdf.__enter__ = Mock(return_value=mock_pdf)
        mock_pdf.__exit__ = Mock(return_value=False)

        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.pdfplumber.open",
            return_value=mock_pdf,
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        text = service._extract_text_from_pdf(mock_pdf_content)

        assert text is not None
        assert "Extracted text from page 1" in text

    def test_extract_text_from_pdf_empty(self, mocker):
        """Returns None when PDF has no extractable text."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        # Mock pdfplumber with no text
        mock_pdf = MagicMock()
        mock_page = MagicMock()
        mock_page.extract_text.return_value = ""
        mock_pdf.pages = [mock_page]
        mock_pdf.__enter__ = Mock(return_value=mock_pdf)
        mock_pdf.__exit__ = Mock(return_value=False)

        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.pdfplumber.open",
            return_value=mock_pdf,
        )

        # Also mock PyPDF as fallback with no text
        mock_reader = MagicMock()
        mock_pypdf_page = MagicMock()
        mock_pypdf_page.extract_text.return_value = ""
        mock_reader.pages = [mock_pypdf_page]
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.PdfReader",
            return_value=mock_reader,
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        text = service._extract_text_from_pdf(b"%PDF-1.4 empty")

        assert text is None


class TestDownloadServiceQueueResearchDownloads:
    """Tests for queue_research_downloads method."""

    def test_queue_research_downloads_success(self, mocker):
        """Queues downloads for downloadable resources."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        # Mock session
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        # Create mock resources
        mock_resource1 = Mock()
        mock_resource1.id = 1
        mock_resource1.url = "https://arxiv.org/abs/2301.00001"

        mock_resource2 = Mock()
        mock_resource2.id = 2
        mock_resource2.url = "https://example.com/page.html"

        mock_session.query.return_value.filter_by.return_value.all.return_value = [
            mock_resource1,
            mock_resource2,
        ]
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock get_default_library_id
        mocker.patch(
            "src.local_deep_research.database.library_init.get_default_library_id",
            return_value="default-lib-id",
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        queued_count = service.queue_research_downloads("research-123")

        # Only arxiv URL should be queued (HTML page is not downloadable)
        assert queued_count >= 0


class TestDownloadServiceDownloadResource:
    """Tests for download_resource method."""

    def test_download_resource_not_found(self, mocker):
        """Returns error when resource not found."""
        mock_settings = Mock()
        mock_settings.get_setting.return_value = "/tmp/test_library"
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_settings_manager",
            return_value=mock_settings,
        )
        mocker.patch("pathlib.Path.mkdir")
        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.RetryManager"
        )

        # Mock session with no resource
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)
        mock_session.query.return_value.get.return_value = None

        mocker.patch(
            "src.local_deep_research.research_library.services.download_service.get_user_db_session",
            return_value=mock_session,
        )

        from src.local_deep_research.research_library.services.download_service import (
            DownloadService,
        )

        service = DownloadService(username="test_user")

        success, reason = service.download_resource(999)

        assert success is False
        assert "not found" in reason.lower()
