"""
Fixtures for research_library tests.
Provides mocked database sessions, HTTP responses, and test data.
"""

import hashlib
import uuid
from contextlib import contextmanager
from typing import Generator

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker

from local_deep_research.database.models import Base
from local_deep_research.database.models.library import (
    Collection,
    Document,
    DocumentCollection,
    DocumentStatus,
    SourceType,
)


# ============== Database Fixtures ==============


@pytest.fixture
def library_engine():
    """Create an in-memory SQLite database with all library models."""
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    yield engine
    engine.dispose()


@pytest.fixture
def library_session(library_engine) -> Generator[Session, None, None]:
    """Create a database session for library testing."""
    SessionLocal = sessionmaker(bind=library_engine)
    session = SessionLocal()
    yield session
    session.close()


@pytest.fixture
def mock_source_type(library_session) -> SourceType:
    """Create a mock SourceType for research downloads."""
    source_type = SourceType(
        id=str(uuid.uuid4()),
        name="research_download",
        display_name="Research Download",
        description="Downloaded from research sources",
        icon="fas fa-download",
    )
    library_session.add(source_type)
    library_session.commit()
    return source_type


@pytest.fixture
def mock_upload_source_type(library_session) -> SourceType:
    """Create a mock SourceType for user uploads."""
    source_type = SourceType(
        id=str(uuid.uuid4()),
        name="user_upload",
        display_name="User Upload",
        description="Uploaded by user",
        icon="fas fa-upload",
    )
    library_session.add(source_type)
    library_session.commit()
    return source_type


@pytest.fixture
def mock_collection(library_session) -> Collection:
    """Create a default Library collection."""
    collection = Collection(
        id=str(uuid.uuid4()),
        name="Library",
        description="Default library collection for research downloads",
        is_default=True,
        collection_type="default_library",
    )
    library_session.add(collection)
    library_session.commit()
    return collection


@pytest.fixture
def mock_user_collection(library_session) -> Collection:
    """Create a user-defined collection."""
    collection = Collection(
        id=str(uuid.uuid4()),
        name="Test Collection",
        description="A test collection for user uploads",
        is_default=False,
        collection_type="user_collection",
    )
    library_session.add(collection)
    library_session.commit()
    return collection


@pytest.fixture
def mock_document(
    library_session, mock_source_type, mock_collection
) -> Document:
    """Create a mock completed document with text content."""
    doc_content = "This is the full text content of a test research paper."
    doc = Document(
        id=str(uuid.uuid4()),
        source_type_id=mock_source_type.id,
        document_hash=hashlib.sha256(doc_content.encode()).hexdigest(),
        original_url="https://arxiv.org/abs/2301.12345",
        file_path="pdfs/arxiv_2301.12345.pdf",
        file_size=1024000,
        file_type="pdf",
        mime_type="application/pdf",
        title="Test Paper on Quantum Computing",
        text_content=doc_content,
        status=DocumentStatus.COMPLETED,
        doi="10.1234/test.2024.001",
        arxiv_id="2301.12345",
    )
    library_session.add(doc)
    library_session.commit()

    # Link document to collection
    doc_coll = DocumentCollection(
        document_id=doc.id,
        collection_id=mock_collection.id,
        indexed=False,
    )
    library_session.add(doc_coll)
    library_session.commit()

    return doc


@pytest.fixture
def mock_pending_document(library_session, mock_source_type) -> Document:
    """Create a mock pending document (not yet downloaded)."""
    doc = Document(
        id=str(uuid.uuid4()),
        source_type_id=mock_source_type.id,
        document_hash=hashlib.sha256(b"pending").hexdigest(),
        original_url="https://arxiv.org/abs/2401.99999",
        file_size=0,
        file_type="pdf",
        status=DocumentStatus.PENDING,
        title="Pending Download Paper",
    )
    library_session.add(doc)
    library_session.commit()
    return doc


# ============== HTTP Response Mocking ==============


@pytest.fixture
def mock_http_response(mocker):
    """Factory for creating mock HTTP responses."""

    def _create_response(
        status_code=200,
        content=b"",
        headers=None,
        json_data=None,
        text=None,
    ):
        response = mocker.Mock()
        response.status_code = status_code
        response.content = content
        response.headers = headers or {"content-type": "application/pdf"}
        response.ok = status_code < 400

        if json_data is not None:
            response.json.return_value = json_data
            response.text = str(json_data)
        elif text is not None:
            response.text = text
        else:
            response.text = (
                content.decode() if isinstance(content, bytes) else str(content)
            )

        return response

    return _create_response


@pytest.fixture
def mock_pdf_content():
    """Return minimal valid PDF bytes for testing."""
    # Minimal valid PDF structure
    return b"""%PDF-1.4
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >> endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
trailer << /Size 4 /Root 1 0 R >>
startxref
193
%%EOF"""


@pytest.fixture
def mock_arxiv_api_response():
    """Mock arXiv API XML response for metadata."""
    return """<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:arxiv="http://arxiv.org/schemas/atom">
    <entry>
        <title>Test Paper on Quantum Computing</title>
        <id>http://arxiv.org/abs/2301.12345</id>
        <summary>This is a test paper abstract about quantum computing advancements.</summary>
        <published>2023-01-15T00:00:00Z</published>
        <author><name>John Doe</name></author>
        <author><name>Jane Smith</name></author>
        <arxiv:category term="quant-ph"/>
        <arxiv:category term="cs.AI"/>
    </entry>
</feed>"""


@pytest.fixture
def mock_pubmed_elink_response():
    """Mock PubMed NCBI elink API response."""
    return {
        "linksets": [{"linksetdbs": [{"dbto": "pmc", "links": ["9876543"]}]}]
    }


@pytest.fixture
def mock_europe_pmc_response():
    """Mock Europe PMC API JSON response."""
    return {
        "resultList": {
            "result": [
                {
                    "id": "12345678",
                    "pmcid": "PMC9876543",
                    "isOpenAccess": "Y",
                    "hasPDF": "Y",
                    "journalTitle": "Nature Medicine",
                    "title": "Test Medical Research Paper",
                }
            ]
        }
    }


@pytest.fixture
def mock_semantic_scholar_paper_response():
    """Mock Semantic Scholar paper API response."""
    return {
        "paperId": "a" * 40,
        "title": "Deep Learning for Natural Language Processing",
        "openAccessPdf": {
            "url": "https://arxiv.org/pdf/2301.12345.pdf",
            "status": "GREEN",
        },
    }


@pytest.fixture
def mock_openalex_work_response():
    """Mock OpenAlex work API response."""
    return {
        "id": "https://openalex.org/W123456789",
        "open_access": {"is_oa": True, "oa_status": "gold"},
        "best_oa_location": {
            "pdf_url": "https://example.com/paper.pdf",
            "landing_page_url": "https://example.com/paper",
        },
    }


@pytest.fixture
def mock_biorxiv_page_html():
    """Mock bioRxiv HTML page for metadata extraction."""
    return """<!DOCTYPE html>
<html>
<head>
    <meta name="DC.Title" content="Novel CRISPR Gene Editing Technique">
    <meta name="DC.Creator" content="Alice Johnson, Bob Williams">
    <meta name="DC.Description" content="We present a novel approach to gene editing using CRISPR-Cas9...">
</head>
<body></body>
</html>"""


# ============== Service Mocking ==============


@pytest.fixture
def mock_settings_manager(mocker):
    """Mock settings manager for download service."""
    mock = mocker.Mock()
    mock.get_setting.side_effect = lambda key, default=None: {
        "research_library.storage_path": "/tmp/test_library",
        "search.engine.web.semantic_scholar.api_key": None,
        "local_search_embedding_model": "all-MiniLM-L6-v2",
        "local_search_embedding_provider": "sentence_transformers",
        "local_search_chunk_size": 1000,
        "local_search_chunk_overlap": 200,
    }.get(key, default)
    return mock


@pytest.fixture
def temp_library_dir(tmp_path):
    """Create temporary library directory structure."""
    library_path = tmp_path / "library"
    library_path.mkdir()
    (library_path / "pdfs").mkdir()
    (library_path / "uploads").mkdir()
    return library_path


@pytest.fixture
def mock_embedding_manager(mocker):
    """Mock LocalEmbeddingManager to avoid loading actual models."""
    mock = mocker.Mock()
    mock.embeddings = mocker.Mock()
    mock.embeddings.embed_query.return_value = [0.1] * 384
    mock.embeddings.embed_documents.return_value = [[0.1] * 384]
    mock._store_chunks_to_db.return_value = ["chunk_1", "chunk_2"]
    mock._delete_chunks_from_db.return_value = 5
    return mock


@pytest.fixture
def mock_rate_tracker(mocker):
    """Mock AdaptiveRateLimitTracker to avoid rate limit delays."""
    mock = mocker.Mock()
    mock.apply_rate_limit.return_value = 0.0  # No wait
    mock.record_outcome.return_value = None
    return mock


# ============== Session Context Mocking ==============


@pytest.fixture
def mock_db_session_context(library_session, mocker):
    """Mock get_user_db_session context manager to return test session."""

    @contextmanager
    def _mock_session(*args, **kwargs):
        yield library_session

    mocker.patch(
        "local_deep_research.database.session_context.get_user_db_session",
        _mock_session,
    )
    return library_session


# ============== Downloader Test Helpers ==============


@pytest.fixture
def mock_requests_session(mocker, mock_pdf_content):
    """Mock requests.Session for all downloaders."""
    mock_session = mocker.Mock()
    mock_session.headers = {"User-Agent": "Test Agent"}

    # Default successful PDF response
    mock_response = mocker.Mock()
    mock_response.status_code = 200
    mock_response.content = mock_pdf_content
    mock_response.headers = {"content-type": "application/pdf"}
    mock_response.ok = True

    mock_session.get.return_value = mock_response
    return mock_session


@pytest.fixture
def create_downloader_with_mock_session(
    mocker, mock_requests_session, mock_rate_tracker
):
    """Factory to create a downloader with mocked session and rate tracker."""

    def _create(downloader_class, **kwargs):
        # Patch the session and rate tracker
        mocker.patch.object(
            downloader_class,
            "__init__",
            lambda self, timeout=30: None,
        )
        downloader = downloader_class.__new__(downloader_class)
        downloader.timeout = kwargs.get("timeout", 30)
        downloader.session = mock_requests_session
        downloader.rate_tracker = mock_rate_tracker
        return downloader

    return _create
