"""
Memory CRUD API Routes for CodeContext server.

Handles memory operations: add, query, list, update, delete.
"""

import asyncio
import time
from typing import Dict

from fastapi import APIRouter, Request, Query

from ..models import (
    MemoryRequest, BatchMemoryRequest, QueryRequest, BatchQueryRequest,
    QuickMemoryRequest, FileSearchRequest
)
from ..services.store_manager import get_store, list_projects, ALL_PROJECTS
from ..services.background import auto_resolve_and_log
from ..utils.formatting import format_memories_datetime
from ..services.summary_generator import calculate_total_tokens


router = APIRouter()


def get_config(request: Request):
    """Get server config from app state."""
    return request.app.state.server_config


def get_base_path(request: Request):
    """Get base path from app state."""
    return request.app.state.base_path


@router.post("/query")
async def query(req: QueryRequest):
    """Single query endpoint."""
    store = get_store(req.project)
    start = time.time()
    results = store.query(req.query, top_k=req.top_k)
    elapsed = round((time.time() - start) * 1000, 2)
    return {
        "query": req.query,
        "project": req.project,
        "results": results,
        "count": len(results),
        "time_ms": elapsed
    }


@router.post("/batch-query")
async def batch_query(req: BatchQueryRequest):
    """Batch query endpoint - multiple queries at once."""
    store = get_store(req.project)
    start = time.time()
    results = store.batch_query(req.queries, top_k=req.top_k)
    elapsed = round((time.time() - start) * 1000, 2)
    return {
        "project": req.project,
        "batch_results": results,
        "query_count": len(req.queries),
        "total_time_ms": elapsed,
        "avg_time_ms": round(elapsed / len(req.queries), 2) if req.queries else 0
    }


@router.post("/memory")
async def add_memory(request: Request, req: MemoryRequest):
    """Add a single memory to a project (with duplicate/conflict detection)."""
    server_config = get_config(request)
    store = get_store(req.project)
    result = store.add(
        obs_type=req.type,
        title=req.title,
        fact=req.fact,
        files=req.files
    )

    # Auto-resolve if enabled and conflict was detected
    auto_resolve_triggered = False
    if (result["status"] == "conflict_flagged"
        and server_config.get("auto_resolve_enabled", False)):

        conflict_id = result.get("conflict_id")
        if conflict_id:
            # Get the full conflict data including existing memory snapshot
            conflict = store.get_conflict_by_id(conflict_id)
            if conflict:
                # Add project field for resolution
                conflict["project"] = req.project

                # Resolve in background (don't block the response)
                asyncio.create_task(
                    auto_resolve_and_log(conflict, store, req.project)
                )
                auto_resolve_triggered = True

    return {
        "id": result["id"],
        "project": req.project,
        "status": result["status"],
        "distance": result.get("distance"),
        "existing_match": result.get("existing_match"),
        "auto_resolve_triggered": auto_resolve_triggered
    }


@router.post("/memory/batch")
async def add_memories_batch(request: Request, req: BatchMemoryRequest):
    """Add multiple memories at once (with duplicate/conflict detection)."""
    server_config = get_config(request)
    store = get_store(req.project)
    memories = [
        {
            "type": m.type,
            "title": m.title,
            "fact": m.fact,
            "files": m.files
        }
        for m in req.memories
    ]
    result = store.add_batch(memories)

    # Auto-resolve conflicts if enabled (mirrors /memory endpoint behavior)
    auto_resolve_triggered = 0
    if server_config.get("auto_resolve_enabled", False) and result["conflicts"]:
        for conflict_info in result["conflicts"]:
            conflict_id = conflict_info.get("conflict_id")
            if conflict_id:
                conflict = store.get_conflict_by_id(conflict_id)
                if conflict:
                    conflict["project"] = req.project
                    asyncio.create_task(
                        auto_resolve_and_log(conflict, store, req.project)
                    )
                    auto_resolve_triggered += 1

    return {
        "project": req.project,
        "added": result["added"],
        "duplicates": result["duplicates"],
        "conflicts": result["conflicts"],
        "duplicate_count": len(result["duplicates"]),
        "conflict_count": len(result["conflicts"]),
        "auto_resolve_triggered": auto_resolve_triggered
    }


@router.get("/models")
async def get_models(request: Request):
    """List available models."""
    return {
        "current": request.app.state.model_name,
        "device": request.app.state.device,
        "available": [
            {"name": "all-MiniLM-L6-v2", "dim": 384, "size": "80MB"},
            {"name": "all-MiniLM-L12-v2", "dim": 384, "size": "120MB"},
            {"name": "all-mpnet-base-v2", "dim": 768, "size": "430MB"}
        ]
    }


@router.get("/stats")
async def stats(request: Request, project: str = Query(default="")):
    """Get store statistics for a project."""
    base_path = get_base_path(request)
    model_name = request.app.state.model_name
    device = request.app.state.device

    if not project:
        # Return global stats
        projects = list_projects()
        total_memories = 0
        total_tokens = 0
        for p in projects:
            store = get_store(p)
            memories = store.list_all(limit=10000)
            total_memories += store.count()
            total_tokens += calculate_total_tokens(memories)

        return {
            "base_path": str(base_path),
            "model": model_name,
            "device": device,
            "project_count": len(projects),
            "total_memories": total_memories,
            "total_tokens": total_tokens,
            "projects": projects
        }
    else:
        store = get_store(project)
        info = store.get_info()
        # Add token count
        memories = store.list_all(limit=10000)
        info["token_count"] = calculate_total_tokens(memories)
        return info


@router.get("/api/files")
async def get_unique_files(project: str = Query(...)):
    """Get all unique files referenced in memories for autocomplete."""
    if project == ALL_PROJECTS:
        # Aggregate unique files from all projects
        projects = list_projects()
        all_files = set()
        for p in projects:
            store = get_store(p)
            all_files.update(store.get_unique_files())
        return {"files": sorted(list(all_files))}
    else:
        store = get_store(project)
        return {"files": store.get_unique_files()}


@router.get("/api/memories")
async def get_memories(
    project: str = Query(...),
    offset: int = Query(default=0),
    limit: int = Query(default=20),
    type: str = Query(default=""),
    file: str = Query(default=""),
    sort: str = Query(default="newest")
):
    """API endpoint for infinite scroll - returns JSON.

    Args:
        project: Project name or __all__ for all projects
        offset: Number of memories to skip
        limit: Max memories to return
        type: Comma-separated list of types to filter (e.g., "bugfix,feature")
        file: Partial file path to filter (case-insensitive)
        sort: Sort order - "newest" (default) or "oldest"
    """
    if not project:
        return {"memories": [], "has_more": False}

    # Parse type filter
    type_filter = [t.strip() for t in type.split(",") if t.strip()] if type else None
    file_filter = file.strip() if file else None
    sort_order = sort if sort in ("newest", "oldest") else "newest"

    if project == ALL_PROJECTS:
        # Aggregate from all projects
        projects = list_projects()
        all_memories = []

        # Fetch extra from each project for proper pagination
        fetch_limit = offset + limit + 10

        for p in projects:
            store = get_store(p)
            p_memories = store.list_all(
                limit=fetch_limit,
                offset=0,
                type_filter=type_filter,
                file_filter=file_filter,
                sort_order=sort_order
            )
            # Add project field
            for m in p_memories:
                m["project"] = p
            all_memories.extend(p_memories)

        # Sort by created_at (direction based on sort_order)
        reverse = sort_order != "oldest"
        all_memories.sort(key=lambda x: x.get("created_at", ""), reverse=reverse)

        # Apply pagination
        paginated = all_memories[offset:offset + limit + 1]  # +1 to check has_more
        has_more = len(paginated) > limit
        if has_more:
            paginated = paginated[:limit]

        memories = format_memories_datetime(paginated)
    else:
        store = get_store(project)
        memories = store.list_all(
            limit=limit + 1,
            offset=offset,
            type_filter=type_filter,
            file_filter=file_filter,
            sort_order=sort_order
        )  # Fetch 1 extra to check has_more
        # Add project field for consistency
        for m in memories:
            m["project"] = project

        has_more = len(memories) > limit
        if has_more:
            memories = memories[:limit]  # Trim to actual limit

        memories = format_memories_datetime(memories)

    return {
        "memories": memories,
        "has_more": has_more,
        "offset": offset,
        "next_offset": offset + limit if has_more else None
    }


@router.get("/api/memories/new")
async def get_new_memories(
    project: str = Query(...),
    since_count: int = Query(...)
):
    """Get memories added since last known count (for auto-refresh)."""
    if project == ALL_PROJECTS:
        # Aggregate counts and new memories from all projects
        projects = list_projects()
        current_count = sum(get_store(p).count() for p in projects)

        if current_count <= since_count:
            return {"memories": [], "total_count": current_count}

        # Fetch recent memories from all projects
        new_count = current_count - since_count
        all_memories = []

        for p in projects:
            store = get_store(p)
            p_memories = store.list_all(limit=new_count + 5, offset=0)
            for m in p_memories:
                m["project"] = p
            all_memories.extend(p_memories)

        # Sort by created_at desc and take the newest
        all_memories.sort(key=lambda x: x.get("created_at", ""), reverse=True)
        memories = all_memories[:new_count]
        memories = format_memories_datetime(memories)

        return {
            "memories": memories,
            "total_count": current_count
        }
    else:
        store = get_store(project)
        current_count = store.count()

        if current_count <= since_count:
            return {"memories": [], "total_count": current_count}

        # Fetch the difference (newest first)
        new_count = current_count - since_count
        memories = store.list_all(limit=new_count, offset=0)
        # Add project field for consistency
        for m in memories:
            m["project"] = project
        memories = format_memories_datetime(memories)

        return {
            "memories": memories,
            "total_count": current_count
        }


@router.get("/api/memory/{memory_id}")
async def get_memory(memory_id: str, project: str = Query(...)):
    """Get a single memory by ID."""
    store = get_store(project)
    memory = store.get_by_id(memory_id)

    if not memory:
        from fastapi import HTTPException
        raise HTTPException(status_code=404, detail=f"Memory {memory_id} not found")

    return memory


@router.delete("/api/memory/{memory_id}")
async def delete_memory(memory_id: str, project: str = Query(...)):
    """Delete a memory by ID."""
    store = get_store(project)
    result = store.delete(memory_id)

    return {
        "success": result.get("success", False),
        "message": result.get("message", ""),
        "archived": result.get("archived", False)
    }


# ============== CLI/MCP Support Endpoints ==============

def auto_classify_learning(learning: str) -> tuple[str, str]:
    """Auto-classify a learning into type and title.

    Returns (type, title) tuple.
    """
    learning_lower = learning.lower()

    # Keywords for classification
    bugfix_keywords = ["fix", "fixed", "bug", "error", "issue", "crash", "broken", "resolve", "solved"]
    gotcha_keywords = ["gotcha", "pitfall", "warning", "caveat", "careful", "watch out", "don't", "never", "always"]
    decision_keywords = ["decided", "chose", "choosing", "decision", "prefer", "instead of", "over"]
    refactor_keywords = ["refactor", "restructure", "reorganize", "cleanup", "clean up", "simplify"]
    optimization_keywords = ["optimize", "performance", "faster", "speed", "efficient", "cache"]
    feature_keywords = ["added", "new feature", "implement", "created", "built"]

    # Determine type
    mem_type = "discovery"  # default

    if any(kw in learning_lower for kw in bugfix_keywords):
        mem_type = "bugfix"
    elif any(kw in learning_lower for kw in gotcha_keywords):
        mem_type = "gotcha"
    elif any(kw in learning_lower for kw in decision_keywords):
        mem_type = "decision"
    elif any(kw in learning_lower for kw in refactor_keywords):
        mem_type = "refactor"
    elif any(kw in learning_lower for kw in optimization_keywords):
        mem_type = "optimization"
    elif any(kw in learning_lower for kw in feature_keywords):
        mem_type = "feature"

    # Generate title (first sentence or first 80 chars)
    title = learning.split('.')[0].strip()
    if len(title) > 80:
        title = title[:77] + "..."

    return mem_type, title


@router.post("/api/memory/quick")
async def quick_memory(request: Request, req: QuickMemoryRequest):
    """Quick capture with auto-classification.

    Analyzes the learning text to determine appropriate type and title,
    then adds the memory.
    """
    server_config = get_config(request)
    store = get_store(req.project)

    # Auto-classify the learning
    mem_type, title = auto_classify_learning(req.learning)

    # Add the memory
    result = store.add(
        obs_type=mem_type,
        title=title,
        fact=req.learning,
        files=req.files
    )

    # Auto-resolve if enabled and conflict was detected
    auto_resolve_triggered = False
    if (result["status"] == "conflict_flagged"
        and server_config.get("auto_resolve_enabled", False)):
        conflict_id = result.get("conflict_id")
        if conflict_id:
            conflict = store.get_conflict_by_id(conflict_id)
            if conflict:
                conflict["project"] = req.project
                asyncio.create_task(
                    auto_resolve_and_log(conflict, store, req.project)
                )
                auto_resolve_triggered = True

    return {
        "id": result["id"],
        "type": mem_type,
        "title": title,
        "fact": req.learning,
        "files": req.files,
        "project": req.project,
        "status": result["status"],
        "auto_classified": True,
        "auto_resolve_triggered": auto_resolve_triggered
    }


@router.post("/api/memories/by-files")
async def memories_by_files(req: FileSearchRequest):
    """Find memories related to specific files.

    Supports three match modes:
    - exact: File path must match exactly
    - contains: File path contains the search string (default)
    - fuzzy: File name (without path) matches
    """
    if req.project == ALL_PROJECTS:
        # Aggregate from all projects
        projects = list_projects()
        all_memories = []

        for p in projects:
            store = get_store(p)
            # Get all memories and filter by files
            memories = store.list_all(limit=1000)

            for mem in memories:
                mem_files = mem.get("files", [])
                if not mem_files:
                    continue

                # Check if any requested file matches any memory file
                for search_file in req.files:
                    for mem_file in mem_files:
                        match = False

                        if req.match_mode == "exact":
                            match = mem_file == search_file
                        elif req.match_mode == "fuzzy":
                            # Match filename only (ignore path)
                            import os
                            search_name = os.path.basename(search_file).lower()
                            mem_name = os.path.basename(mem_file).lower()
                            match = search_name in mem_name or mem_name in search_name
                        else:  # contains (default)
                            match = search_file.lower() in mem_file.lower() or mem_file.lower() in search_file.lower()

                        if match:
                            mem["project"] = p
                            all_memories.append(mem)
                            break
                    if mem in all_memories:
                        break

        # Remove duplicates (same id)
        seen_ids = set()
        unique_memories = []
        for mem in all_memories:
            if mem["id"] not in seen_ids:
                seen_ids.add(mem["id"])
                unique_memories.append(mem)

        memories = format_memories_datetime(unique_memories)
    else:
        store = get_store(req.project)
        all_memories = store.list_all(limit=1000)
        matching = []

        for mem in all_memories:
            mem_files = mem.get("files", [])
            if not mem_files:
                continue

            for search_file in req.files:
                for mem_file in mem_files:
                    match = False

                    if req.match_mode == "exact":
                        match = mem_file == search_file
                    elif req.match_mode == "fuzzy":
                        import os
                        search_name = os.path.basename(search_file).lower()
                        mem_name = os.path.basename(mem_file).lower()
                        match = search_name in mem_name or mem_name in search_name
                    else:  # contains
                        match = search_file.lower() in mem_file.lower() or mem_file.lower() in search_file.lower()

                    if match:
                        mem["project"] = req.project
                        matching.append(mem)
                        break
                if mem in matching:
                    break

        memories = format_memories_datetime(matching)

    return {
        "results": memories,
        "count": len(memories),
        "files_searched": req.files,
        "match_mode": req.match_mode
    }


@router.get("/api/memories/recent")
async def recent_memories(
    project: str = Query(...),
    limit: int = Query(default=10, ge=1, le=50)
):
    """Get the N most recent memories.

    Returns memories sorted by creation date (newest first).
    """
    if project == ALL_PROJECTS:
        # Aggregate from all projects
        projects = list_projects()
        all_memories = []

        for p in projects:
            store = get_store(p)
            p_memories = store.list_all(limit=limit, offset=0, sort_order="newest")
            for m in p_memories:
                m["project"] = p
            all_memories.extend(p_memories)

        # Sort by created_at desc
        all_memories.sort(key=lambda x: x.get("created_at", ""), reverse=True)
        memories = all_memories[:limit]
    else:
        store = get_store(project)
        memories = store.list_all(limit=limit, offset=0, sort_order="newest")
        for m in memories:
            m["project"] = project

    memories = format_memories_datetime(memories)

    return {
        "results": memories,
        "count": len(memories),
        "project": project
    }
