"""
Extended tests for library models - Comprehensive coverage of unified document architecture.

Tests cover:
- Document model operations
- Collection model operations
- DocumentCollection (many-to-many) operations
- DocumentChunk model operations
- DownloadQueue model operations
- RAGIndex model operations
- LibraryStatistics model operations
- CollectionFolder and CollectionFolderFile models
"""

import uuid

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

from local_deep_research.database.models import Base
from local_deep_research.database.models.library import (
    Document,
    DocumentBlob,
    Collection,
    DocumentCollection,
    DocumentChunk,
    DownloadQueue,
    LibraryStatistics,
    RAGIndex,
    CollectionFolder,
    CollectionFolderFile,
    SourceType,
    UploadBatch,
    DocumentStatus,
    RAGIndexStatus,
    EmbeddingProvider,
)


@pytest.fixture
def engine():
    """Create in-memory SQLite engine."""
    engine = create_engine("sqlite:///:memory:", echo=False)
    Base.metadata.create_all(engine)
    yield engine
    engine.dispose()


@pytest.fixture
def session(engine):
    """Create database session."""
    Session = sessionmaker(bind=engine)
    session = Session()
    yield session
    session.close()


@pytest.fixture
def source_type(session):
    """Create a source type for testing."""
    st = SourceType(
        id=str(uuid.uuid4()),
        name="research_download",
        display_name="Research Download",
        description="Downloaded from research sources",
    )
    session.add(st)
    session.commit()
    return st


@pytest.fixture
def collection(session):
    """Create a collection for testing."""
    coll = Collection(
        id=str(uuid.uuid4()),
        name="Test Collection",
        description="A test collection",
        collection_type="user_collection",
        is_default=False,
    )
    session.add(coll)
    session.commit()
    return coll


class TestDocumentModel:
    """Tests for Document model."""

    def test_create_document(self, session, source_type):
        """Should create a document."""
        doc = Document(
            id=str(uuid.uuid4()),
            source_type_id=source_type.id,
            document_hash="abc123def456" * 5 + "ab",  # 64 chars
            file_size=1024,
            file_type="pdf",
            status=DocumentStatus.COMPLETED,
        )
        session.add(doc)
        session.commit()

        assert doc.id is not None
        assert doc.file_size == 1024

    def test_document_with_all_fields(self, session, source_type):
        """Should create document with all optional fields."""
        doc = Document(
            id=str(uuid.uuid4()),
            source_type_id=source_type.id,
            document_hash="xyz789" * 10 + "abcd",
            file_size=2048,
            file_type="pdf",
            status=DocumentStatus.COMPLETED,
            title="Test Paper",
            description="A test paper description",
            authors=["Author One", "Author Two"],
            doi="10.1234/test.doi",
            arxiv_id="2301.00001",
            text_content="This is the paper content...",
            extraction_method="pdf_extraction",
            extraction_source="pdfplumber",
            extraction_quality="high",
            tags=["machine-learning", "nlp"],
        )
        session.add(doc)
        session.commit()

        retrieved = session.query(Document).filter_by(id=doc.id).first()
        assert retrieved.title == "Test Paper"
        assert retrieved.authors == ["Author One", "Author Two"]

    def test_document_status_enum(self, session, source_type):
        """Document status should use enum values."""
        doc = Document(
            id=str(uuid.uuid4()),
            source_type_id=source_type.id,
            document_hash="status123" * 7 + "a",
            file_size=512,
            file_type="txt",
            status=DocumentStatus.PENDING,
        )
        session.add(doc)
        session.commit()

        assert doc.status == DocumentStatus.PENDING

    def test_document_unique_hash(self, session, source_type):
        """Document hash should be unique."""
        hash_value = "unique_hash" * 5 + "abcd"

        doc1 = Document(
            id=str(uuid.uuid4()),
            source_type_id=source_type.id,
            document_hash=hash_value,
            file_size=100,
            file_type="pdf",
            status=DocumentStatus.COMPLETED,
        )
        session.add(doc1)
        session.commit()

        doc2 = Document(
            id=str(uuid.uuid4()),
            source_type_id=source_type.id,
            document_hash=hash_value,  # Same hash
            file_size=200,
            file_type="pdf",
            status=DocumentStatus.COMPLETED,
        )
        session.add(doc2)

        with pytest.raises(Exception):  # IntegrityError
            session.commit()

    def test_document_repr(self, session, source_type):
        """Document __repr__ should work."""
        doc = Document(
            id=str(uuid.uuid4()),
            source_type_id=source_type.id,
            document_hash="repr_test" * 8,
            file_size=100,
            file_type="pdf",
            title="Repr Test",
            status=DocumentStatus.COMPLETED,
        )
        repr_str = repr(doc)
        assert "Document" in repr_str


class TestCollectionModel:
    """Tests for Collection model."""

    def test_create_collection(self, session):
        """Should create a collection."""
        coll = Collection(
            id=str(uuid.uuid4()),
            name="My Collection",
            description="Test description",
        )
        session.add(coll)
        session.commit()

        assert coll.id is not None
        assert coll.name == "My Collection"

    def test_collection_default_values(self, session):
        """Collection should have correct defaults."""
        coll = Collection(
            id=str(uuid.uuid4()),
            name="Default Test",
        )
        session.add(coll)
        session.commit()

        assert coll.is_default is False
        assert coll.collection_type == "user_collection"

    def test_collection_with_embedding_config(self, session):
        """Collection can store embedding configuration."""
        coll = Collection(
            id=str(uuid.uuid4()),
            name="Embedding Collection",
            embedding_model="all-MiniLM-L6-v2",
            embedding_model_type=EmbeddingProvider.SENTENCE_TRANSFORMERS,
            embedding_dimension=384,
            chunk_size=512,
            chunk_overlap=50,
        )
        session.add(coll)
        session.commit()

        retrieved = session.query(Collection).filter_by(id=coll.id).first()
        assert retrieved.embedding_model == "all-MiniLM-L6-v2"
        assert retrieved.embedding_dimension == 384

    def test_collection_repr(self, session):
        """Collection __repr__ should work."""
        coll = Collection(
            id="test-id",
            name="Repr Test",
            collection_type="user_collection",
        )
        repr_str = repr(coll)
        assert "Collection" in repr_str


class TestDocumentCollectionModel:
    """Tests for DocumentCollection many-to-many model."""

    def test_link_document_to_collection(
        self, session, source_type, collection
    ):
        """Should link document to collection."""
        doc = Document(
            id=str(uuid.uuid4()),
            source_type_id=source_type.id,
            document_hash="link_test" * 8,
            file_size=100,
            file_type="pdf",
            status=DocumentStatus.COMPLETED,
        )
        session.add(doc)
        session.commit()

        link = DocumentCollection(
            document_id=doc.id,
            collection_id=collection.id,
            indexed=False,
            chunk_count=0,
        )
        session.add(link)
        session.commit()

        assert link.id is not None

    def test_document_collection_unique_pair(
        self, session, source_type, collection
    ):
        """Document-collection pair should be unique."""
        doc = Document(
            id=str(uuid.uuid4()),
            source_type_id=source_type.id,
            document_hash="unique_pair" * 6 + "ab",
            file_size=100,
            file_type="pdf",
            status=DocumentStatus.COMPLETED,
        )
        session.add(doc)
        session.commit()

        link1 = DocumentCollection(
            document_id=doc.id,
            collection_id=collection.id,
        )
        session.add(link1)
        session.commit()

        link2 = DocumentCollection(
            document_id=doc.id,
            collection_id=collection.id,  # Same pair
        )
        session.add(link2)

        with pytest.raises(Exception):  # IntegrityError
            session.commit()

    def test_document_collection_indexing_status(
        self, session, source_type, collection
    ):
        """Should track indexing status per collection."""
        doc = Document(
            id=str(uuid.uuid4()),
            source_type_id=source_type.id,
            document_hash="index_status" * 6 + "12",
            file_size=100,
            file_type="pdf",
            status=DocumentStatus.COMPLETED,
        )
        session.add(doc)
        session.commit()

        link = DocumentCollection(
            document_id=doc.id,
            collection_id=collection.id,
            indexed=True,
            chunk_count=25,
        )
        session.add(link)
        session.commit()

        retrieved = (
            session.query(DocumentCollection)
            .filter_by(document_id=doc.id)
            .first()
        )
        assert retrieved.indexed is True
        assert retrieved.chunk_count == 25


class TestDocumentChunkModel:
    """Tests for DocumentChunk model."""

    def test_create_document_chunk(self, session):
        """Should create a document chunk."""
        chunk = DocumentChunk(
            chunk_hash="chunk_hash" * 6 + "ab",
            source_type="document",
            source_id=str(uuid.uuid4()),
            collection_name="collection_abc123",
            chunk_text="This is the chunk text content.",
            chunk_index=0,
            start_char=0,
            end_char=31,
            word_count=6,
            embedding_id=str(uuid.uuid4()),
            embedding_model="all-MiniLM-L6-v2",
            embedding_model_type=EmbeddingProvider.SENTENCE_TRANSFORMERS,
        )
        session.add(chunk)
        session.commit()

        assert chunk.id is not None

    def test_chunk_unique_per_collection(self, session):
        """Chunk hash should be unique per collection."""
        chunk_hash = "duplicate_hash" * 5

        chunk1 = DocumentChunk(
            chunk_hash=chunk_hash,
            source_type="document",
            collection_name="collection_1",
            chunk_text="Content 1",
            chunk_index=0,
            start_char=0,
            end_char=10,
            word_count=2,
            embedding_id=str(uuid.uuid4()),
            embedding_model="model",
            embedding_model_type=EmbeddingProvider.SENTENCE_TRANSFORMERS,
        )
        session.add(chunk1)
        session.commit()

        # Same hash, same collection should fail
        chunk2 = DocumentChunk(
            chunk_hash=chunk_hash,
            source_type="document",
            collection_name="collection_1",  # Same collection
            chunk_text="Content 2",
            chunk_index=1,
            start_char=10,
            end_char=20,
            word_count=2,
            embedding_id=str(uuid.uuid4()),
            embedding_model="model",
            embedding_model_type=EmbeddingProvider.SENTENCE_TRANSFORMERS,
        )
        session.add(chunk2)

        with pytest.raises(Exception):
            session.commit()

    def test_chunk_repr(self, session):
        """DocumentChunk __repr__ should work."""
        chunk = DocumentChunk(
            chunk_hash="repr_test" * 8,
            source_type="document",
            collection_name="test_collection",
            chunk_text="Test content",
            chunk_index=5,
            start_char=100,
            end_char=200,
            word_count=10,
            embedding_id=str(uuid.uuid4()),
            embedding_model="model",
            embedding_model_type=EmbeddingProvider.SENTENCE_TRANSFORMERS,
        )
        repr_str = repr(chunk)
        assert "DocumentChunk" in repr_str


class TestRAGIndexModel:
    """Tests for RAGIndex model."""

    def test_create_rag_index(self, session):
        """Should create a RAG index."""
        index = RAGIndex(
            collection_name="collection_abc",
            embedding_model="all-MiniLM-L6-v2",
            embedding_model_type=EmbeddingProvider.SENTENCE_TRANSFORMERS,
            embedding_dimension=384,
            index_path="/data/indexes/collection_abc.faiss",
            index_hash="index_hash" * 6 + "ab",
            chunk_size=512,
            chunk_overlap=50,
            status=RAGIndexStatus.ACTIVE,
        )
        session.add(index)
        session.commit()

        assert index.id is not None

    def test_rag_index_status_transitions(self, session):
        """RAG index status can transition."""
        index = RAGIndex(
            collection_name="status_test",
            embedding_model="model",
            embedding_model_type=EmbeddingProvider.OLLAMA,
            embedding_dimension=768,
            index_path="/path/to/index.faiss",
            index_hash="status_hash" * 6 + "ab",
            chunk_size=256,
            chunk_overlap=25,
            status=RAGIndexStatus.ACTIVE,
        )
        session.add(index)
        session.commit()

        # Update status
        index.status = RAGIndexStatus.REBUILDING
        session.commit()

        retrieved = session.query(RAGIndex).filter_by(id=index.id).first()
        assert retrieved.status == RAGIndexStatus.REBUILDING

    def test_rag_index_repr(self, session):
        """RAGIndex __repr__ should work."""
        index = RAGIndex(
            collection_name="repr_collection",
            embedding_model="test-model",
            embedding_model_type=EmbeddingProvider.SENTENCE_TRANSFORMERS,
            embedding_dimension=384,
            index_path="/path/index.faiss",
            index_hash="repr_hash" * 8,
            chunk_size=512,
            chunk_overlap=50,
            chunk_count=100,
        )
        repr_str = repr(index)
        assert "RAGIndex" in repr_str


class TestLibraryStatisticsModel:
    """Tests for LibraryStatistics model."""

    def test_create_statistics(self, session):
        """Should create library statistics."""
        stats = LibraryStatistics(
            total_documents=100,
            total_pdfs=80,
            total_html=15,
            total_other=5,
            total_size_bytes=1024000,
            average_document_size=10240,
        )
        session.add(stats)
        session.commit()

        assert stats.id is not None

    def test_statistics_download_metrics(self, session):
        """Statistics should track download metrics."""
        stats = LibraryStatistics(
            total_documents=50,
            total_download_attempts=100,
            successful_downloads=45,
            failed_downloads=5,
            pending_downloads=50,
        )
        session.add(stats)
        session.commit()

        retrieved = (
            session.query(LibraryStatistics).filter_by(id=stats.id).first()
        )
        assert retrieved.total_download_attempts == 100
        assert retrieved.successful_downloads == 45

    def test_statistics_repr(self, session):
        """LibraryStatistics __repr__ should work."""
        stats = LibraryStatistics(
            total_documents=50,
            total_size_bytes=500000,
        )
        repr_str = repr(stats)
        assert "LibraryStatistics" in repr_str


class TestDownloadQueueModel:
    """Tests for DownloadQueue model."""

    def test_create_queue_item(self, session, collection):
        """Should create a download queue item."""
        # Note: This requires a ResearchResource to exist
        # For now, test the model structure
        queue = DownloadQueue.__table__
        columns = {c.name for c in queue.columns}

        assert "resource_id" in columns
        assert "research_id" in columns
        assert "priority" in columns
        assert "status" in columns
        assert "attempts" in columns


class TestCollectionFolderModel:
    """Tests for CollectionFolder model."""

    def test_create_collection_folder(self, session, collection):
        """Should create a collection folder link."""
        folder = CollectionFolder(
            collection_id=collection.id,
            folder_path="/home/user/documents/research",
            include_patterns=["*.pdf", "*.txt"],
            recursive=True,
        )
        session.add(folder)
        session.commit()

        assert folder.id is not None

    def test_folder_default_patterns(self, session, collection):
        """Folder should have default include patterns."""
        folder = CollectionFolder(
            collection_id=collection.id,
            folder_path="/path/to/folder",
        )
        session.add(folder)
        session.commit()

        # Default patterns should include common document types
        assert folder.include_patterns is not None

    def test_folder_repr(self, session, collection):
        """CollectionFolder __repr__ should work."""
        folder = CollectionFolder(
            collection_id=collection.id,
            folder_path="/test/path",
            file_count=10,
        )
        repr_str = repr(folder)
        assert "CollectionFolder" in repr_str


class TestCollectionFolderFileModel:
    """Tests for CollectionFolderFile model."""

    def test_create_folder_file(self, session, collection):
        """Should create a folder file entry."""
        folder = CollectionFolder(
            collection_id=collection.id,
            folder_path="/test/folder",
        )
        session.add(folder)
        session.commit()

        file = CollectionFolderFile(
            folder_id=folder.id,
            relative_path="subdir/document.pdf",
            file_hash="file_hash" * 8,
            file_size=2048,
            file_type="pdf",
            indexed=False,
        )
        session.add(file)
        session.commit()

        assert file.id is not None

    def test_folder_file_unique_path(self, session, collection):
        """File path should be unique within folder."""
        folder = CollectionFolder(
            collection_id=collection.id,
            folder_path="/unique/test",
        )
        session.add(folder)
        session.commit()

        file1 = CollectionFolderFile(
            folder_id=folder.id,
            relative_path="same/path.pdf",
        )
        session.add(file1)
        session.commit()

        file2 = CollectionFolderFile(
            folder_id=folder.id,
            relative_path="same/path.pdf",  # Same path
        )
        session.add(file2)

        with pytest.raises(Exception):
            session.commit()

    def test_folder_file_repr(self):
        """CollectionFolderFile __repr__ should work."""
        file = CollectionFolderFile(
            relative_path="test/file.pdf",
            indexed=True,
        )
        repr_str = repr(file)
        assert "CollectionFolderFile" in repr_str


class TestSourceTypeModel:
    """Tests for SourceType model."""

    def test_create_source_type(self, session):
        """Should create a source type."""
        st = SourceType(
            id=str(uuid.uuid4()),
            name="user_upload",
            display_name="User Upload",
            description="Uploaded by user",
            icon="upload",
        )
        session.add(st)
        session.commit()

        assert st.id is not None

    def test_source_type_unique_name(self, session):
        """Source type name should be unique."""
        st1 = SourceType(
            id=str(uuid.uuid4()),
            name="unique_type",
            display_name="Unique Type",
        )
        session.add(st1)
        session.commit()

        st2 = SourceType(
            id=str(uuid.uuid4()),
            name="unique_type",  # Same name
            display_name="Another Unique Type",
        )
        session.add(st2)

        with pytest.raises(Exception):
            session.commit()

    def test_source_type_repr(self):
        """SourceType __repr__ should work."""
        st = SourceType(
            id="test-id",
            name="test_type",
            display_name="Test Type",
        )
        repr_str = repr(st)
        assert "SourceType" in repr_str


class TestDocumentBlobModel:
    """Tests for DocumentBlob model."""

    def test_create_document_blob(self, session, source_type):
        """Should create a document blob."""
        doc = Document(
            id=str(uuid.uuid4()),
            source_type_id=source_type.id,
            document_hash="blob_test" * 8,
            file_size=1000,
            file_type="pdf",
            status=DocumentStatus.COMPLETED,
        )
        session.add(doc)
        session.commit()

        blob = DocumentBlob(
            document_id=doc.id,
            pdf_binary=b"PDF binary content here",
            blob_hash="binary_hash" * 6 + "ab",
        )
        session.add(blob)
        session.commit()

        retrieved = (
            session.query(DocumentBlob).filter_by(document_id=doc.id).first()
        )
        assert retrieved.pdf_binary == b"PDF binary content here"

    def test_blob_repr(self, session, source_type):
        """DocumentBlob __repr__ should work."""
        doc = Document(
            id="test-doc-id-" + "x" * 24,
            source_type_id=source_type.id,
            document_hash="repr_blob" * 8,
            file_size=100,
            file_type="pdf",
            status=DocumentStatus.COMPLETED,
        )
        blob = DocumentBlob(
            document_id=doc.id,
            pdf_binary=b"test",
        )
        repr_str = repr(blob)
        assert "DocumentBlob" in repr_str


@pytest.mark.filterwarnings(
    "ignore:UploadBatch is deprecated.*:DeprecationWarning"
)
class TestUploadBatchModel:
    """Tests for UploadBatch model (deprecated — see PR 11a)."""

    def test_create_upload_batch(self, session, collection):
        """Should create an upload batch."""
        batch = UploadBatch(
            id=str(uuid.uuid4()),
            collection_id=collection.id,
            file_count=5,
            total_size=10240,
        )
        session.add(batch)
        session.commit()

        assert batch.id is not None
        assert batch.file_count == 5

    def test_batch_repr(self):
        """UploadBatch __repr__ should work."""
        batch = UploadBatch(
            id="test-batch-id",
            file_count=3,
            total_size=5000,
        )
        repr_str = repr(batch)
        assert "UploadBatch" in repr_str
