"""
RAG Service Factory

Provides get_rag_service() for creating LibraryRAGService instances
with appropriate settings. Extracted from rag_routes.py to avoid
circular imports (service → routes).
"""

import json
from typing import Optional

from loguru import logger

from ...database.models.library import Collection
from ...database.session_context import get_user_db_session
from ...utilities.db_utils import get_settings_manager
from ...utilities.type_utils import to_bool
from ..services.library_rag_service import LibraryRAGService


def get_rag_service(
    username: str,
    collection_id: Optional[str] = None,
    use_defaults: bool = False,
    db_password: Optional[str] = None,
) -> LibraryRAGService:
    """
    Get RAG service instance with appropriate settings.

    Args:
        username: Username for database access and settings lookup
        collection_id: Optional collection UUID to load stored settings from
        use_defaults: When True, ignore stored collection settings and use
            current defaults. Pass True on force-reindex so that the new
            default embedding model is picked up.
        db_password: Optional database password for encrypted databases

    If collection_id is provided:
    - Uses collection's stored settings if they exist (unless use_defaults=True)
    - Uses current defaults for new collections (and stores them)

    If no collection_id:
    - Uses current default settings
    """
    # Use get_user_db_session so that settings are readable from background
    # threads (no Flask app context).  Without an explicit db_session,
    # get_settings_manager falls back to JSON defaults only, and the
    # local_search_* keys have no JSON defaults — causing user-configured
    # embedding settings to be silently ignored.  See #3453.
    with get_user_db_session(username, db_password) as db_session:
        settings = get_settings_manager(
            db_session=db_session, username=username
        )

        # Get current default settings.
        # The local_search_* keys are written by the embedding-settings page
        # and have no JSON defaults file yet, so explicit fallbacks are
        # required to avoid TypeError / None propagation on fresh installs.
        raw_embedding_model = settings.get_setting(
            "local_search_embedding_model"
        )
        raw_embedding_provider = settings.get_setting(
            "local_search_embedding_provider"
        )
        # Warn on silent fallback so a regression of #3453 is visible in logs
        # instead of being masked by `or`-chained defaults.  On fresh installs
        # this fires legitimately until the user saves settings; in a
        # regression it would fire on every indexing call.
        if not raw_embedding_model and not raw_embedding_provider:
            logger.warning(
                "local_search embedding settings are empty; falling back to "
                "hardcoded defaults (sentence_transformers/all-MiniLM-L6-v2). "
                "Expected on fresh installs before settings are saved; "
                "otherwise check that db_session is being passed to "
                "SettingsManager (see #3453)."
            )
        default_embedding_model = raw_embedding_model or "all-MiniLM-L6-v2"
        default_embedding_provider = (
            raw_embedding_provider or "sentence_transformers"
        )
        default_chunk_size = int(
            settings.get_setting("local_search_chunk_size") or 1000
        )
        default_chunk_overlap = int(
            settings.get_setting("local_search_chunk_overlap") or 200
        )
        default_splitter_type = (
            settings.get_setting("local_search_splitter_type") or "recursive"
        )
        default_text_separators = settings.get_setting(
            "local_search_text_separators"
        )
        # Parse JSON string to list
        if isinstance(default_text_separators, str):
            try:
                default_text_separators = json.loads(default_text_separators)
            except json.JSONDecodeError:
                logger.warning(
                    "Invalid JSON for local_search_text_separators: {!r} — using default separators",
                    default_text_separators,
                )
                default_text_separators = ["\n\n", "\n", ". ", " ", ""]
        elif default_text_separators is None:
            default_text_separators = ["\n\n", "\n", ". ", " ", ""]
        default_distance_metric = (
            settings.get_setting("local_search_distance_metric") or "cosine"
        )
        default_normalize_vectors = settings.get_bool_setting(
            "local_search_normalize_vectors"
        )
        default_index_type = (
            settings.get_setting("local_search_index_type") or "Flat"
        )

        # If collection_id provided, check for stored settings
        if collection_id:
            collection = (
                db_session.query(Collection).filter_by(id=collection_id).first()
            )

            if collection and collection.embedding_model and not use_defaults:
                # Use collection's stored settings
                logger.info(
                    f"Using stored settings for collection {collection_id}: "
                    f"{collection.embedding_model_type.value if collection.embedding_model_type else 'unknown'}/{collection.embedding_model}"
                )
                # Handle normalize_vectors - may be stored as string in some
                # cases
                coll_normalize = collection.normalize_vectors
                if coll_normalize is not None:
                    coll_normalize = to_bool(coll_normalize)
                else:
                    coll_normalize = default_normalize_vectors

                def _col(stored, default):
                    """Use stored collection value if not None, else default."""
                    return stored if stored is not None else default

                return LibraryRAGService(
                    username=username,
                    embedding_model=collection.embedding_model,
                    embedding_provider=collection.embedding_model_type.value
                    if collection.embedding_model_type
                    else default_embedding_provider,
                    chunk_size=_col(collection.chunk_size, default_chunk_size),
                    chunk_overlap=_col(
                        collection.chunk_overlap, default_chunk_overlap
                    ),
                    splitter_type=_col(
                        collection.splitter_type, default_splitter_type
                    ),
                    text_separators=_col(
                        collection.text_separators, default_text_separators
                    ),
                    distance_metric=_col(
                        collection.distance_metric, default_distance_metric
                    ),
                    normalize_vectors=coll_normalize,
                    index_type=_col(collection.index_type, default_index_type),
                    db_password=db_password,
                )
            if collection:
                # New collection - use defaults and store them
                logger.info(
                    f"New collection {collection_id}, using and storing default settings"
                )

                # Create service with defaults
                return LibraryRAGService(
                    username=username,
                    embedding_model=default_embedding_model,
                    embedding_provider=default_embedding_provider,
                    chunk_size=default_chunk_size,
                    chunk_overlap=default_chunk_overlap,
                    splitter_type=default_splitter_type,
                    text_separators=default_text_separators,
                    distance_metric=default_distance_metric,
                    normalize_vectors=default_normalize_vectors,
                    index_type=default_index_type,
                    db_password=db_password,
                )

                # Store settings on collection (will be done during indexing)
                # Note: We don't store here because we don't have
                # embedding_dimension yet.  It will be stored in
                # index_collection when first document is indexed.

        # No collection or fallback - use current defaults
        return LibraryRAGService(
            username=username,
            embedding_model=default_embedding_model,
            embedding_provider=default_embedding_provider,
            chunk_size=default_chunk_size,
            chunk_overlap=default_chunk_overlap,
            splitter_type=default_splitter_type,
            text_separators=default_text_separators,
            distance_metric=default_distance_metric,
            normalize_vectors=default_normalize_vectors,
            index_type=default_index_type,
            db_password=db_password,
        )
