"""
Database initialization for Library - Unified Document Architecture.

This module handles:
- Seeding source_types table with predefined types
- Creating the default "Library" collection
- Must be called on app startup for each user
"""

import threading
import uuid
from loguru import logger
from sqlalchemy.exc import IntegrityError

from .models import SourceType, Collection
from .session_context import get_user_db_session
from ..constants import (
    RESEARCH_HISTORY_COLLECTION_NAME,
    RESEARCH_HISTORY_COLLECTION_DESCRIPTION,
)


# Per-user locks serialise the check-then-insert critical sections below.
# Under IMMEDIATE isolation this was unnecessary; under DEFERRED, two
# concurrent invocations (e.g. two logins of the same user from two
# browser tabs) could both see the absent row and both insert, creating
# duplicate default collections. An application-level lock is simpler
# than a migration adding a partial UNIQUE constraint, and cheap.
_user_init_locks: dict[str, threading.Lock] = {}
_user_init_locks_lock = threading.Lock()


def _get_user_init_lock(username: str) -> threading.Lock:
    """Get (or lazily create) the per-user lock used to serialise the
    check-then-insert idempotent collection initialisers.
    """
    with _user_init_locks_lock:
        lock = _user_init_locks.get(username)
        if lock is None:
            lock = threading.Lock()
            _user_init_locks[username] = lock
        return lock


def pop_user_init_lock(username: str) -> None:
    """Remove the per-user init lock for ``username`` from the registry.

    Called from the user-close path (``db_manager.close_user_database``
    callers in ``web/auth/connection_cleanup.py`` and ``web/auth/routes.py``)
    so the module-level dict doesn't accumulate one entry per username
    across the process lifetime. The next login lazily re-creates the
    lock, which is fine — the lock has no state that needs to persist
    across login/logout.
    """
    with _user_init_locks_lock:
        _user_init_locks.pop(username, None)


def seed_source_types(username: str, password: str = None) -> None:
    """
    Seed the source_types table with predefined document source types.

    Args:
        username: User to seed types for
        password: User's password (optional, uses session context)
    """
    predefined_types = [
        {
            "name": "research_download",
            "display_name": "Research Download",
            "description": "Documents downloaded from research sessions (arXiv, PubMed, etc.)",
            "icon": "download",
        },
        {
            "name": "user_upload",
            "display_name": "User Upload",
            "description": "Documents manually uploaded by the user",
            "icon": "upload",
        },
        {
            "name": "manual_entry",
            "display_name": "Manual Entry",
            "description": "Documents manually created or entered",
            "icon": "edit",
        },
        {
            "name": "research_report",
            "display_name": "Research Report",
            "description": "Generated research reports (markdown) for semantic search",
            "icon": "file-alt",
        },
        {
            "name": "research_source",
            "display_name": "Research Source",
            "description": "Sources discovered during research with content for semantic search",
            "icon": "link",
        },
    ]

    try:
        with get_user_db_session(username, password) as session:
            for type_data in predefined_types:
                # Check if type already exists
                existing = (
                    session.query(SourceType)
                    .filter_by(name=type_data["name"])
                    .first()
                )

                if not existing:
                    source_type = SourceType(id=str(uuid.uuid4()), **type_data)
                    session.add(source_type)
                    logger.info(f"Created source type: {type_data['name']}")

            session.commit()
            logger.info("Source types seeded successfully")

    except IntegrityError:
        logger.warning("Source types may already exist")
    except Exception:
        logger.warning("Error seeding source types")
        raise


def ensure_default_library_collection(
    username: str, password: str = None
) -> str:
    """
    Ensure the default "Library" collection exists for a user.
    Creates it if it doesn't exist.

    Args:
        username: User to check/create library for
        password: User's password (optional, uses session context)

    Returns:
        UUID of the Library collection
    """
    try:
        with (
            _get_user_init_lock(username),
            get_user_db_session(username, password) as session,
        ):
            # Check if default library exists
            library = (
                session.query(Collection).filter_by(is_default=True).first()
            )

            if library:
                logger.debug(f"Default Library collection exists: {library.id}")
                return library.id

            # Create default Library collection
            library_id = str(uuid.uuid4())
            library = Collection(
                id=library_id,
                name="Library",
                description="Default collection for research downloads and documents",
                collection_type="default_library",
                is_default=True,
            )
            session.add(library)
            session.commit()

            logger.info(f"Created default Library collection: {library_id}")
            return library_id

    except Exception:
        logger.warning("Error ensuring default Library collection")
        raise


def ensure_research_history_collection(
    username: str, password: str = None
) -> str:
    """
    Ensure the "Research History" collection exists for a user.
    This collection is used for semantic search over research reports and sources.
    Creates it if it doesn't exist.

    Args:
        username: User to check/create collection for
        password: User's password (optional, uses session context)

    Returns:
        UUID of the Research History collection
    """
    try:
        with (
            _get_user_init_lock(username),
            get_user_db_session(username, password) as session,
        ):
            # Check if research history collection exists
            collection = (
                session.query(Collection)
                .filter_by(collection_type="research_history")
                .first()
            )

            if collection:
                logger.debug(
                    f"Research History collection exists: {collection.id}"
                )
                return collection.id

            # Create Research History collection
            collection_id = str(uuid.uuid4())
            collection = Collection(
                id=collection_id,
                name=RESEARCH_HISTORY_COLLECTION_NAME,
                description=RESEARCH_HISTORY_COLLECTION_DESCRIPTION,
                collection_type="research_history",
                is_default=False,
            )
            session.add(collection)
            session.commit()

            logger.info(f"Created Research History collection: {collection_id}")
            return collection_id

    except Exception:
        logger.warning("Error ensuring Research History collection")
        raise


def initialize_library_for_user(username: str, password: str = None) -> dict:
    """
    Complete initialization of library system for a user.
    Seeds source types and ensures default Library and Research History collections exist.

    Args:
        username: User to initialize for
        password: User's password (optional, uses session context)

    Returns:
        Dict with initialization results
    """
    results = {
        "source_types_seeded": False,
        "library_collection_id": None,
        "research_history_collection_id": None,
        "success": False,
    }

    try:
        # Seed source types
        seed_source_types(username, password)
        results["source_types_seeded"] = True

        # Ensure Library collection
        library_id = ensure_default_library_collection(username, password)
        results["library_collection_id"] = library_id

        # Ensure Research History collection
        research_history_id = ensure_research_history_collection(
            username, password
        )
        results["research_history_collection_id"] = research_history_id

        results["success"] = True
        logger.info(f"Library initialization complete for user: {username}")

    except Exception as e:
        logger.warning(f"Library initialization failed for {username}")
        results["error"] = str(e)

    return results


def get_default_library_id(username: str, password: str = None) -> str:
    """
    Get the ID of the default Library collection for a user.
    Creates it if it doesn't exist.

    Args:
        username: User to get library for
        password: User's password (optional, uses session context)

    Returns:
        UUID of the Library collection
    """
    return ensure_default_library_collection(username, password)


def get_source_type_id(
    username: str, type_name: str, password: str = None
) -> str:
    """
    Get the ID of a source type by name.

    Args:
        username: User to query for
        type_name: Name of source type (e.g., 'research_download', 'user_upload')
        password: User's password (optional, uses session context)

    Returns:
        UUID of the source type

    Raises:
        ValueError: If source type not found
    """
    try:
        with get_user_db_session(username, password) as session:
            source_type = (
                session.query(SourceType).filter_by(name=type_name).first()
            )

            if not source_type:
                raise ValueError(f"Source type not found: {type_name}")  # noqa: TRY301 — inside db session context, except logs and re-raises

            return source_type.id

    except Exception:
        logger.warning("Error getting source type ID")
        raise
