"""
Card search router for MTG card database.

Provides endpoints for searching and retrieving MTG card data
with filters for type, set, and color.
"""
from typing import List, Optional, Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

from app.core.database import mtg_get_db
from app.core.redis_client import cache_get, cache_set
from app.services.card_search_service import CardSearchService
from app.services.deck_suggestion_service import DeckSuggestionService
from app.services.fuzzy_card_matcher import FuzzyCardMatcher
from app.models.user_deck import UserDeck
from app.models.mtg_models import MtgCard
from app.schemas.card_search_schemas import CardSearchResponse, CardResponse, SetResponse, CardTypeResponse

router = APIRouter(prefix="/api/cards", tags=["Card Search"])


@router.get("/search", response_model=CardSearchResponse)
async def search_cards_endpoint(
    q: str = Query(..., min_length=1, description="Search query"),
    card_type: Optional[str] = Query(None, description="Filter by card type"),
    set_code: Optional[str] = Query(None, description="Filter by set code"),
    color: Optional[str] = Query(None, description="Filter by color (e.g., WU, BR)"),
    limit: int = Query(100, ge=1, le=500, description="Maximum results"),
    offset: int = Query(0, ge=0, description="Number of results to skip"),
    db: AsyncSession = Depends(mtg_get_db),
):
    """
    Search cards with filters.
    
    Supports filtering by type, set, and color in addition to name search.
    Uses fuzzy matching as a fallback when exact/partial matches are not found.
    """
    cache_key = f"card_search:{q}:{card_type}:{set_code}:{color}:{limit}:{offset}"
    
    # Check cache first
    cached = await cache_get(cache_key)
    if cached:
        return {"cached": True, "results": cached}
    
    # Normalize the search query for consistency
    normalized_query = FuzzyCardMatcher.normalize_card_name(q)
    
    # Search cards using the existing service
    results = await CardSearchService.search_cards(
        db=db,
        query=q,
        card_type=card_type,
        set_code=set_code,
        color=color,
        limit=limit,
        offset=offset,
    )
    
    # If no results found, try fuzzy matching as a fallback
    if results["total"] == 0:
        fuzzy_results = await _fuzzy_search_fallback(
            db=db,
            query=q,
            normalized_query=normalized_query,
            card_type=card_type,
            set_code=set_code,
            color=color,
            limit=limit,
            offset=offset,
        )
        results = fuzzy_results
    
    # Add fuzzy matching metadata to results
    results["query_normalized"] = normalized_query
    results["fuzzy_match"] = True
    
    # Cache results for 5 minutes
    await cache_set(cache_key, str(results), ttl=300)
    
    return {"cached": False, "results": results}


async def _fuzzy_search_fallback(
    db: AsyncSession,
    query: str,
    normalized_query: str,
    card_type: Optional[str] = None,
    set_code: Optional[str] = None,
    color: Optional[str] = None,
    limit: int = 100,
    offset: int = 0,
) -> Dict[str, Any]:
    """
    Fallback fuzzy search when exact/partial matches yield no results.
    
    Fetches all cards matching the type/set/color filters, then uses
    fuzzy matching to find the best card name matches.
    """
    from sqlalchemy import select, or_
    
    # Fetch candidate cards based on non-name filters
    conditions = []
    if card_type:
        conditions.append(MtgCard.type_line.ilike(f"%{card_type}%"))
    if set_code:
        conditions.append(MtgCard.set_code == set_code)
    if color:
        colors = [c.strip() for c in color.upper().split(",")]
        for c in colors:
            if c in ["W", "U", "B", "R", "G"]:
                conditions.append(MtgCard.colors.ilike(f"%{c}%"))
    
    # If no filters, fetch a broader set for fuzzy matching
    if not conditions:
        stmt = select(MtgCard).limit(limit * 5)
    else:
        stmt = select(MtgCard).where(*conditions).limit(limit * 5)
    
    result = await db.execute(stmt)
    candidate_cards = result.scalars().all()
    
    if not candidate_cards:
        return {
            "cards": [],
            "total": 0,
            "page": offset // limit + 1,
            "page_size": limit,
            "total_pages": 0,
            "fuzzy_fallback": True,
            "message": "No cards found matching your query.",
        }
    
    # Build candidate name list and lookup
    candidate_names = [card.name for card in candidate_cards if card.name]
    card_lookup = {card.name.lower(): card for card in candidate_cards if card.name}
    
    # Use fuzzy matching to find best matches
    matched_name, confidence, match_type = FuzzyCardMatcher.find_best_match(
        normalized_query, candidate_names, threshold=FuzzyCardMatcher.MIN_MATCH_THRESHOLD
    )
    
    # Build results from fuzzy matches
    card_list = []
    if matched_name and matched_name.lower() in card_lookup:
        card = card_lookup[matched_name.lower()]
        card_data = {
            "id": card.id,
            "name": card.name,
            "mana_cost": card.mana_cost,
            "type_line": card.type_line,
            "oracle_text": card.oracle_text,
            "power": card.power,
            "toughness": card.toughness,
            "rarity": card.rarity,
            "layout": card.layout,
            "colors": card.colors,
            "set_code": card.set_code,
            "set_name": card.set_name,
            "fuzzy_match": True,
            "match_confidence": confidence,
            "match_type": match_type,
            "original_query": query,
        }
        card_list.append(card_data)
    
    return {
        "cards": card_list,
        "total": len(card_list),
        "page": offset // limit + 1,
        "page_size": limit,
        "total_pages": (len(card_list) + limit - 1) // limit if card_list else 0,
        "fuzzy_fallback": True,
        "match_type": match_type,
        "confidence": confidence,
    }


@router.get("/{card_id}", response_model=CardResponse)
async def get_card_endpoint(
    card_id: int,
    db: AsyncSession = Depends(mtg_get_db),
):
    """
    Get a specific card by ID.
    """
    cache_key = f"card_by_id:{card_id}"
    
    # Check cache first
    cached = await cache_get(cache_key)
    if cached:
        return {"cached": True, "results": cached}
    
    # Get card
    card = await CardSearchService.get_card_by_id(db, card_id)
    
    if not card:
        raise HTTPException(status_code=404, detail="Card not found")
    
    # Cache for 10 minutes
    await cache_set(cache_key, str(card), ttl=600)
    
    return {"cached": False, "results": card}


@router.get("/sets", response_model=List[SetResponse])
async def get_sets_endpoint(
    db: AsyncSession = Depends(mtg_get_db),
):
    """
    Get all available sets.
    """
    cache_key = "all_sets:all"
    
    cached = await cache_get(cache_key)
    if cached:
        return {"cached": True, "results": cached}
    
    sets = await CardSearchService.get_sets(db)
    
    # Cache for 1 hour
    await cache_set(cache_key, str(sets), ttl=3600)
    
    return {"cached": False, "results": sets}


@router.get("/types", response_model=List[CardTypeResponse])
async def get_card_types_endpoint(
    db: AsyncSession = Depends(mtg_get_db),
):
    """
    Get all unique card types.
    """
    cache_key = "card_types:all"
    
    cached = await cache_get(cache_key)
    if cached:
        return {"cached": True, "results": cached}
    
    types = await CardSearchService.get_card_types(db)
    
    # Cache for 30 minutes
    await cache_set(cache_key, str(types), ttl=1800)
    
    return {"cached": False, "results": types}


@router.get("/rarities", response_model=List[str])
async def get_card_rarities_endpoint(
    db: AsyncSession = Depends(mtg_get_db),
):
    """
    Get all unique card rarities.
    """
    cache_key = "card_rarities:all"
    
    cached = await cache_get(cache_key)
    if cached:
        return {"cached": True, "results": cached}
    
    rarities = await CardSearchService.get_card_rarities(db)
    
    # Cache for 30 minutes
    await cache_set(cache_key, str(rarities), ttl=1800)
    
    return {"cached": False, "results": rarities}


@router.get("/suggest", response_model=List[Dict[str, Any]])
async def suggest_cards_endpoint(
    deck_id: int = Query(..., description="Deck ID to suggest cards for"),
    limit: int = Query(20, ge=1, le=100, description="Maximum suggestions"),
    db: AsyncSession = Depends(mtg_get_db),
):
    """
    Suggest similar cards for a deck.
    
    Matches by: same type, same color, same set, same mana cost,
    and cards often paired in existing user decks.
    """
    # Verify deck exists
    stmt = select(UserDeck).where(UserDeck.id == deck_id)
    result = await db.execute(stmt)
    deck = result.scalar_one_or_none()
    
    if not deck:
        raise HTTPException(status_code=404, detail="Deck not found")
    
    suggestions = await DeckSuggestionService.suggest_cards(db, deck_id, limit)
    
    return suggestions
