"""
Cascade helper for deletion operations.

Handles cleanup of related records that don't have proper FK constraints:
- DocumentChunk (source_id has no FK constraint)
- FAISS index files
- Filesystem files
"""

from pathlib import Path
from typing import Dict, List, Optional, Any

from loguru import logger
from sqlalchemy.orm import Session

from ....constants import FILE_PATH_SENTINELS
from ....database.models.library import (
    Document,
    DocumentBlob,
    DocumentChunk,
    DocumentCollection,
    RAGIndex,
)
from ....database.models.download_tracker import DownloadTracker


class CascadeHelper:
    """Helper class for cleaning up related records during deletion."""

    @staticmethod
    def delete_document_chunks(
        session: Session,
        document_id: str,
        collection_name: Optional[str] = None,
    ) -> int:
        """
        Delete DocumentChunks for a document.

        Since DocumentChunk.source_id has no FK constraint, we must manually
        clean up chunks when deleting a document.

        Args:
            session: Database session
            document_id: The document ID to delete chunks for
            collection_name: Optional collection name to limit deletion scope

        Returns:
            Number of chunks deleted
        """
        query = session.query(DocumentChunk).filter(
            DocumentChunk.source_id == document_id,
            DocumentChunk.source_type == "document",
        )

        if collection_name:
            query = query.filter(
                DocumentChunk.collection_name == collection_name
            )

        count = query.delete(synchronize_session=False)
        logger.debug(
            f"Deleted {count} chunks for document {document_id[:8]}..."
            + (f" in collection {collection_name}" if collection_name else "")
        )
        return count

    @staticmethod
    def delete_collection_chunks(
        session: Session,
        collection_name: str,
    ) -> int:
        """
        Delete all DocumentChunks for a collection.

        Args:
            session: Database session
            collection_name: The collection name (e.g., "collection_<uuid>")

        Returns:
            Number of chunks deleted
        """
        count = (
            session.query(DocumentChunk)
            .filter_by(collection_name=collection_name)
            .delete(synchronize_session=False)
        )
        logger.debug(f"Deleted {count} chunks for collection {collection_name}")
        return count

    @staticmethod
    def get_document_blob_size(session: Session, document_id: str) -> int:
        """
        Get the size of a document's blob in bytes.

        Args:
            session: Database session
            document_id: The document ID

        Returns:
            Size in bytes, or 0 if no blob exists
        """
        blob = (
            session.query(DocumentBlob)
            .filter_by(document_id=document_id)
            .first()
        )
        if blob and blob.pdf_binary:
            return len(blob.pdf_binary)
        return 0

    @staticmethod
    def delete_document_blob(session: Session, document_id: str) -> int:
        """
        Delete a document's blob record.

        Note: This is typically handled by CASCADE, but can be called explicitly
        for blob-only deletion.

        Args:
            session: Database session
            document_id: The document ID

        Returns:
            Size of deleted blob in bytes
        """
        blob = (
            session.query(DocumentBlob)
            .filter_by(document_id=document_id)
            .first()
        )
        if blob:
            size = len(blob.pdf_binary) if blob.pdf_binary else 0
            session.delete(blob)
            logger.debug(
                f"Deleted blob for document {document_id[:8]}... ({size} bytes)"
            )
            return size
        return 0

    @staticmethod
    def delete_filesystem_file(file_path: Optional[str]) -> bool:
        """
        Delete a file from the filesystem.

        Args:
            file_path: Path to the file (can be relative or absolute)

        Returns:
            True if file was deleted, False otherwise
        """
        if not file_path:
            return False

        # Skip special path markers
        if file_path in FILE_PATH_SENTINELS:
            return False

        try:
            path = Path(file_path)
            if path.is_file():
                path.unlink()
                logger.debug(f"Deleted filesystem file: {file_path}")
                return True
        except Exception:
            logger.exception(f"Failed to delete filesystem file: {file_path}")
        return False

    @staticmethod
    def delete_faiss_index_files(index_path: Optional[str]) -> bool:
        """
        Delete FAISS index files.

        FAISS stores indices as .faiss and .pkl files.

        Args:
            index_path: Path to the FAISS index file (without extension)

        Returns:
            True if files were deleted, False otherwise
        """
        if not index_path:
            return False

        try:
            path = Path(index_path)
            deleted_any = False

            # FAISS index file
            faiss_file = path.with_suffix(".faiss")
            if faiss_file.is_file():
                faiss_file.unlink()
                logger.debug(f"Deleted FAISS index file: {faiss_file}")
                deleted_any = True

            # Pickle file for metadata
            pkl_file = path.with_suffix(".pkl")
            if pkl_file.is_file():
                pkl_file.unlink()
                logger.debug(f"Deleted FAISS pkl file: {pkl_file}")
                deleted_any = True

            return deleted_any
        except Exception:
            logger.exception(f"Failed to delete FAISS files for: {index_path}")
        return False

    @staticmethod
    def delete_rag_indices_for_collection(
        session: Session,
        collection_name: str,
    ) -> Dict[str, Any]:
        """
        Delete RAGIndex records and their FAISS files for a collection.

        Args:
            session: Database session
            collection_name: The collection name (e.g., "collection_<uuid>")

        Returns:
            Dict with deletion results
        """
        indices = (
            session.query(RAGIndex)
            .filter_by(collection_name=collection_name)
            .all()
        )

        deleted_indices = 0
        deleted_files = 0

        for index in indices:
            # Delete FAISS files
            if CascadeHelper.delete_faiss_index_files(str(index.index_path)):
                deleted_files += 1

            session.delete(index)
            deleted_indices += 1

        logger.debug(
            f"Deleted {deleted_indices} RAGIndex records and {deleted_files} "
            f"FAISS files for collection {collection_name}"
        )

        return {
            "deleted_indices": deleted_indices,
            "deleted_files": deleted_files,
        }

    @staticmethod
    def update_download_tracker(
        session: Session,
        document: Document,
    ) -> bool:
        """
        Update DownloadTracker when a document is deleted.

        The FK has SET NULL, but we also need to update is_downloaded flag.

        Args:
            session: Database session
            document: The document being deleted

        Returns:
            True if tracker was updated
        """
        if not document.original_url:
            return False

        # Get URL hash using the same method as library_service
        from ...utils import get_url_hash

        try:
            url_hash = get_url_hash(str(document.original_url))
            tracker = (
                session.query(DownloadTracker)
                .filter_by(url_hash=url_hash)
                .first()
            )

            if tracker:
                tracker.is_downloaded = False  # type: ignore[assignment]
                tracker.file_path = None  # type: ignore[assignment]
                logger.debug(
                    f"Updated DownloadTracker for document {document.id[:8]}..."
                )
                return True
        except Exception:
            logger.exception("Failed to update DownloadTracker")
        return False

    @staticmethod
    def count_document_in_collections(
        session: Session,
        document_id: str,
    ) -> int:
        """
        Count how many collections a document is in.

        Args:
            session: Database session
            document_id: The document ID

        Returns:
            Number of collections the document is in
        """
        return (
            session.query(DocumentCollection)
            .filter_by(document_id=document_id)
            .count()
        )

    @staticmethod
    def get_document_collections(
        session: Session,
        document_id: str,
    ) -> List[str]:
        """
        Get all collection IDs a document belongs to.

        Args:
            session: Database session
            document_id: The document ID

        Returns:
            List of collection IDs
        """
        doc_collections = (
            session.query(DocumentCollection.collection_id)
            .filter_by(document_id=document_id)
            .all()
        )
        return [dc.collection_id for dc in doc_collections]

    @staticmethod
    def remove_from_faiss_index(
        username: str,
        collection_name: str,
        chunk_ids: List[str],
    ) -> bool:
        """
        Remove specific chunks from a FAISS index.

        Args:
            username: Username for RAG service
            collection_name: Collection name
            chunk_ids: List of chunk IDs to remove

        Returns:
            True if successful
        """
        try:
            from ...services.library_rag_service import LibraryRAGService

            with LibraryRAGService(
                username=username,
            ) as rag_service:
                # This uses the existing remove functionality
                if (
                    hasattr(rag_service, "faiss_index")
                    and rag_service.faiss_index
                ):
                    if hasattr(rag_service.faiss_index, "delete"):
                        rag_service.faiss_index.delete(chunk_ids)
                        return True
        except Exception:
            logger.exception("Failed to remove chunks from FAISS index")
        return False

    @staticmethod
    def delete_document_completely(
        session: Session,
        document_id: str,
    ) -> bool:
        """
        Delete a document and all related records using query-based deletes.

        This avoids ORM cascade issues where SQLAlchemy tries to set
        DocumentBlob.document_id to NULL (which fails because it's a PK).

        Deletes in order:
        1. DocumentBlob
        2. DocumentCollection links
        3. Document itself

        Note: DocumentChunks should be deleted separately before calling this,
        as they may need collection-specific handling.

        Args:
            session: Database session
            document_id: The document ID to delete

        Returns:
            True if document was deleted
        """
        # Delete blob (has document_id as PK, can't be nulled by cascade)
        session.query(DocumentBlob).filter_by(document_id=document_id).delete(
            synchronize_session=False
        )

        # Delete collection links
        session.query(DocumentCollection).filter_by(
            document_id=document_id
        ).delete(synchronize_session=False)

        # Delete document itself
        deleted = (
            session.query(Document)
            .filter_by(id=document_id)
            .delete(synchronize_session=False)
        )

        if deleted:
            logger.debug(f"Deleted document {document_id[:8]}... completely")

        return deleted > 0
