"""
Semantic Search Router

Handles CLIP-based semantic search operations:
- Text-based image/video search
- Similar file search
- Embedding generation and management
- Model settings
"""

import asyncio
import time
from typing import Dict, Optional

from fastapi import APIRouter, BackgroundTasks, Body, Depends, Query, Request
from pydantic import BaseModel
from slowapi import Limiter
from slowapi.util import get_remote_address

from ..core.dependencies import get_current_user, require_admin, get_app_state
from ..core.exceptions import handle_exceptions, ValidationError
from ..core.path_tokens import encode_path
from modules.semantic_search import get_semantic_search
from modules.universal_logger import get_logger

logger = get_logger('API')

router = APIRouter(prefix="/api/semantic", tags=["Semantic Search"])
limiter = Limiter(key_func=get_remote_address)

# Batch limit for embedding generation
EMBEDDING_BATCH_LIMIT = 10000


# ============================================================================
# PYDANTIC MODELS
# ============================================================================

class SemanticSearchRequest(BaseModel):
    query: str
    limit: int = 50
    platform: Optional[str] = None
    source: Optional[str] = None
    threshold: float = 0.2


class GenerateEmbeddingsRequest(BaseModel):
    limit: int = 100
    platform: Optional[str] = None


class SemanticSettingsUpdate(BaseModel):
    model: Optional[str] = None
    threshold: Optional[float] = None


# ============================================================================
# SEARCH ENDPOINTS
# ============================================================================

@router.get("/stats")
@limiter.limit("120/minute")
@handle_exceptions
async def get_semantic_stats(
    request: Request,
    current_user: Dict = Depends(get_current_user)
):
    """Get statistics about semantic search embeddings."""
    app_state = get_app_state()
    search = get_semantic_search(app_state.db)
    stats = search.get_embedding_stats()
    return stats


@router.post("/search")
@limiter.limit("30/minute")
@handle_exceptions
async def semantic_search(
    request: Request,
    current_user: Dict = Depends(get_current_user),
    query: str = Body(..., embed=True),
    limit: int = Body(50, ge=1, le=200),
    platform: Optional[str] = Body(None),
    source: Optional[str] = Body(None),
    threshold: float = Body(0.2, ge=0.0, le=1.0)
):
    """Search for images/videos using natural language query."""
    app_state = get_app_state()
    search = get_semantic_search(app_state.db)
    results = search.search_by_text(
        query=query,
        limit=limit,
        platform=platform,
        source=source,
        threshold=threshold
    )
    for r in results:
        fp = r.get('file_path')
        r['file_token'] = encode_path(fp) if fp else None
    return {"results": results, "count": len(results), "query": query}


@router.post("/similar/{file_id}")
@limiter.limit("30/minute")
@handle_exceptions
async def find_similar_files(
    request: Request,
    file_id: int,
    current_user: Dict = Depends(get_current_user),
    limit: int = Query(50, ge=1, le=200),
    platform: Optional[str] = Query(None),
    source: Optional[str] = Query(None),
    threshold: float = Query(0.5, ge=0.0, le=1.0)
):
    """Find files similar to a given file."""
    app_state = get_app_state()
    search = get_semantic_search(app_state.db)
    results = search.search_by_file_id(
        file_id=file_id,
        limit=limit,
        platform=platform,
        source=source,
        threshold=threshold
    )
    for r in results:
        fp = r.get('file_path')
        r['file_token'] = encode_path(fp) if fp else None
    return {"results": results, "count": len(results), "source_file_id": file_id}


# ============================================================================
# EMBEDDING GENERATION ENDPOINTS
# ============================================================================

@router.post("/generate")
@limiter.limit("10/minute")
@handle_exceptions
async def generate_embeddings(
    request: Request,
    background_tasks: BackgroundTasks,
    current_user: Dict = Depends(get_current_user),
    limit: int = Body(100, ge=1, le=1000),
    platform: Optional[str] = Body(None)
):
    """Generate CLIP embeddings for files that don't have them yet."""
    app_state = get_app_state()

    if app_state.indexing_running:
        return {
            "success": False,
            "message": "Indexing already in progress",
            "already_running": True,
            "status": "already_running"
        }

    search = get_semantic_search(app_state.db)

    app_state.indexing_running = True
    app_state.indexing_start_time = time.time()

    loop = asyncio.get_event_loop()
    manager = getattr(app_state, 'websocket_manager', None)

    def progress_callback(processed: int, total: int, current_file: str):
        try:
            if processed % 10 == 0 or processed == total:
                stats = search.get_embedding_stats()
                if manager:
                    asyncio.run_coroutine_threadsafe(
                        manager.broadcast({
                            "type": "embedding_progress",
                            "processed": processed,
                            "total": total,
                            "current_file": current_file,
                            "total_embeddings": stats.get('total_embeddings', 0),
                            "coverage_percent": stats.get('coverage_percent', 0)
                        }),
                        loop
                    )
        except Exception:
            pass

    def run_generation():
        try:
            results = search.generate_embeddings_batch(
                limit=limit,
                platform=platform,
                progress_callback=progress_callback
            )
            logger.info(f"Embedding generation complete: {results}", module="SemanticSearch")
            try:
                stats = search.get_embedding_stats()
                if manager:
                    asyncio.run_coroutine_threadsafe(
                        manager.broadcast({
                            "type": "embedding_complete",
                            "results": results,
                            "total_embeddings": stats.get('total_embeddings', 0),
                            "coverage_percent": stats.get('coverage_percent', 0)
                        }),
                        loop
                    )
            except Exception:
                pass
        except Exception as e:
            logger.error(f"Embedding generation failed: {e}", module="SemanticSearch")
        finally:
            app_state.indexing_running = False
            app_state.indexing_start_time = None

    background_tasks.add_task(run_generation)

    return {
        "message": f"Started embedding generation for up to {limit} files",
        "status": "processing"
    }


@router.get("/status")
@limiter.limit("60/minute")
@handle_exceptions
async def get_semantic_indexing_status(
    request: Request,
    current_user: Dict = Depends(get_current_user)
):
    """Check if semantic indexing is currently running."""
    app_state = get_app_state()
    elapsed = None
    if app_state.indexing_running and app_state.indexing_start_time:
        elapsed = int(time.time() - app_state.indexing_start_time)
    return {
        "indexing_running": app_state.indexing_running,
        "elapsed_seconds": elapsed
    }


# ============================================================================
# SETTINGS ENDPOINTS
# ============================================================================

@router.get("/settings")
@limiter.limit("30/minute")
@handle_exceptions
async def get_semantic_settings(
    request: Request,
    current_user: Dict = Depends(get_current_user)
):
    """Get semantic search settings."""
    app_state = get_app_state()
    settings = app_state.settings.get('semantic_search', {})
    return {
        "model": settings.get('model', 'clip-ViT-B-32'),
        "threshold": settings.get('threshold', 0.2)
    }


@router.post("/settings")
@limiter.limit("10/minute")
@handle_exceptions
async def update_semantic_settings(
    request: Request,
    background_tasks: BackgroundTasks,
    current_user: Dict = Depends(require_admin),
    model: str = Body(None, embed=True),
    threshold: float = Body(None, embed=True)
):
    """Update semantic search settings."""
    app_state = get_app_state()

    current_settings = app_state.settings.get('semantic_search', {}) or {}
    old_model = current_settings.get('model', 'clip-ViT-B-32') if isinstance(current_settings, dict) else 'clip-ViT-B-32'
    model_changed = False

    new_settings = dict(current_settings) if isinstance(current_settings, dict) else {}

    if model:
        valid_models = ['clip-ViT-B-32', 'clip-ViT-B-16', 'clip-ViT-L-14']
        if model not in valid_models:
            raise ValidationError(f"Invalid model. Must be one of: {valid_models}")
        if model != old_model:
            model_changed = True
        new_settings['model'] = model

    if threshold is not None:
        if threshold < 0 or threshold > 1:
            raise ValidationError("Threshold must be between 0 and 1")
        new_settings['threshold'] = threshold

    app_state.settings.set('semantic_search', new_settings, category='ai')

    if model_changed:
        logger.info(f"Model changed from {old_model} to {model}, clearing embeddings", module="SemanticSearch")

        with app_state.db.get_connection(for_write=True) as conn:
            cursor = conn.cursor()
            cursor.execute('DELETE FROM content_embeddings')
            deleted = cursor.rowcount
            logger.info(f"Cleared {deleted} embeddings for model change", module="SemanticSearch")

        def run_reindex_for_model_change():
            try:
                search = get_semantic_search(app_state.db, force_reload=True)
                results = search.generate_embeddings_batch(limit=EMBEDDING_BATCH_LIMIT)
                logger.info(f"Model change re-index complete: {results}", module="SemanticSearch")
            except Exception as e:
                logger.error(f"Model change re-index failed: {e}", module="SemanticSearch")

        background_tasks.add_task(run_reindex_for_model_change)

        logger.info(f"Semantic search settings updated: {new_settings} (re-indexing started)", module="SemanticSearch")
        return {"success": True, "settings": new_settings, "reindexing": True, "message": f"Model changed to {model}, re-indexing started"}

    logger.info(f"Semantic search settings updated: {new_settings}", module="SemanticSearch")
    return {"success": True, "settings": new_settings, "reindexing": False}


@router.post("/reindex")
@limiter.limit("2/minute")
@handle_exceptions
async def reindex_embeddings(
    request: Request,
    background_tasks: BackgroundTasks,
    current_user: Dict = Depends(require_admin)
):
    """Clear and regenerate all embeddings."""
    app_state = get_app_state()

    if app_state.indexing_running:
        return {"success": False, "message": "Indexing already in progress", "already_running": True}

    search = get_semantic_search(app_state.db)

    with app_state.db.get_connection(for_write=True) as conn:
        cursor = conn.cursor()
        cursor.execute('DELETE FROM content_embeddings')
        deleted = cursor.rowcount
        logger.info(f"Cleared {deleted} embeddings for reindexing", module="SemanticSearch")

    app_state.indexing_running = True
    app_state.indexing_start_time = time.time()

    def run_reindex():
        try:
            results = search.generate_embeddings_batch(limit=EMBEDDING_BATCH_LIMIT)
            logger.info(f"Reindex complete: {results}", module="SemanticSearch")
        except Exception as e:
            logger.error(f"Reindex failed: {e}", module="SemanticSearch")
        finally:
            app_state.indexing_running = False
            app_state.indexing_start_time = None

    background_tasks.add_task(run_reindex)

    return {"success": True, "message": "Re-indexing started in background"}


@router.post("/clear")
@limiter.limit("2/minute")
@handle_exceptions
async def clear_embeddings(
    request: Request,
    current_user: Dict = Depends(require_admin)
):
    """Clear all embeddings."""
    app_state = get_app_state()

    with app_state.db.get_connection(for_write=True) as conn:
        cursor = conn.cursor()
        cursor.execute('DELETE FROM content_embeddings')
        deleted = cursor.rowcount

    logger.info(f"Cleared {deleted} embeddings", module="SemanticSearch")
    return {"success": True, "deleted": deleted}
