"""Flashcard decks and cards — generate, browse, edit, delete."""
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy import or_, text as sa_text
from sqlalchemy.orm import Session

from app.database import get_db
from app.models.flashcard import FlashcardDeck, Flashcard, FlashcardDeckRating
from app.models.section import Section
from app.models.user import User
from app.utils.auth import get_current_user, require_moderator

router = APIRouter()


# ── Schemas ──────────────────────────────────────────────────────────

class FlashcardDeckCreate(BaseModel):
    model_config = {"protected_namespaces": ()}

    section_id: int
    title: str
    model_id: str | None = None


class FlashcardDeckUpdate(BaseModel):
    title: str | None = None


class FlashcardDeckResponse(BaseModel):
    id: int
    title: str
    section_id: int | None = None
    user_id: int
    card_count: int
    is_shared: int = 0
    created_at: object
    deleted_at: object | None = None

    class Config:
        from_attributes = True


class RatingRequest(BaseModel):
    rating: int  # 1-5


class FlashcardCardResponse(BaseModel):
    id: int
    deck_id: int
    front: str
    back: str
    page_reference: int | None = None
    image_path: str | None = None
    created_at: object

    class Config:
        from_attributes = True


class FlashcardDeckDetail(BaseModel):
    id: int
    title: str
    section_id: int | None = None
    user_id: int
    card_count: int
    created_at: object
    cards: list[FlashcardCardResponse]

    class Config:
        from_attributes = True


class CardEdit(BaseModel):
    front: str | None = None
    back: str | None = None


def _own_deck_or_404(deck_id: int, current_user: User, db: Session) -> FlashcardDeck:
    deck = db.query(FlashcardDeck).filter(FlashcardDeck.id == deck_id).first()
    if not deck:
        raise HTTPException(status_code=404, detail="Deck not found")
    if deck.user_id != current_user.id and not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Not your deck")
    return deck


# ── Deck endpoints ───────────────────────────────────────────────────

@router.post("/")
def create_flashcard_deck(
    data: FlashcardDeckCreate,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_moderator),
):
    """Start async flashcard generation from a section. Returns {job_id} immediately."""
    import uuid

    section = db.query(Section).filter(Section.id == data.section_id).first()
    if not section:
        raise HTTPException(status_code=404, detail="Section not found")

    job_id = str(uuid.uuid4())

    try:
        from app.tasks.quiz_tasks import generate_flashcard_deck
        import redis as redis_lib
        from app.config import settings

        r = redis_lib.from_url(settings.REDIS_URL, decode_responses=True)
        r.set(f"extraction:status:{job_id}", "pending", ex=3600)
        r.lpush(f"extraction:user_jobs:{current_user.id}", job_id)
        r.expire(f"extraction:user_jobs:{current_user.id}", 86400)
        r.set(f"extraction:job_title:{job_id}", data.title, ex=3600)

        generate_flashcard_deck.delay(
            job_id=job_id,
            user_id=current_user.id,
            section_id=data.section_id,
            title=data.title,
            model_id=data.model_id,
        )
    except Exception:
        raise HTTPException(status_code=503, detail="Task queue unavailable")

    return {"job_id": job_id, "status": "pending"}


@router.get("/", response_model=list[FlashcardDeckResponse])
def list_flashcard_decks(
    include_deleted: bool = Query(False),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """List flashcard decks belonging to current user."""
    q = db.query(FlashcardDeck).filter(FlashcardDeck.user_id == current_user.id)
    if not include_deleted:
        q = q.filter(FlashcardDeck.deleted_at.is_(None))
    return q.order_by(FlashcardDeck.created_at.desc()).all()


@router.get("/trash", response_model=list[FlashcardDeckResponse])
def list_trashed_decks(
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """List soft-deleted decks for current user's trash."""
    return db.query(FlashcardDeck).filter(
        FlashcardDeck.user_id == current_user.id,
        FlashcardDeck.deleted_at.isnot(None),
    ).order_by(FlashcardDeck.deleted_at.desc()).all()


@router.get("/shared")
def list_shared_decks(
    limit: int = Query(20, le=100),
    offset: int = Query(0),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """List shared decks sorted by average rating (desc), with pagination."""
    from sqlalchemy import func

    avg_rating = db.query(
        FlashcardDeckRating.deck_id,
        func.avg(FlashcardDeckRating.rating).label("avg_rating"),
        func.count(FlashcardDeckRating.id).label("rating_count"),
    ).group_by(FlashcardDeckRating.deck_id).subquery()

    my_ratings = {}
    for r in db.query(FlashcardDeckRating).filter(FlashcardDeckRating.user_id == current_user.id).all():
        my_ratings[r.deck_id] = r.rating

    total = db.query(FlashcardDeck).filter(
        FlashcardDeck.is_shared == 1,
        FlashcardDeck.deleted_at.is_(None),
    ).count()

    decks_with_rating = db.query(
        FlashcardDeck,
        func.coalesce(avg_rating.c.avg_rating, 0).label("avg_rating"),
        func.coalesce(avg_rating.c.rating_count, 0).label("rating_count"),
    ).outerjoin(
        avg_rating, FlashcardDeck.id == avg_rating.c.deck_id
    ).filter(
        FlashcardDeck.is_shared == 1,
        FlashcardDeck.deleted_at.is_(None),
    ).order_by(
        func.coalesce(avg_rating.c.avg_rating, 0).desc(),
        FlashcardDeck.created_at.desc(),
    ).offset(offset).limit(limit).all()

    user_cache: dict[int, str] = {}
    result = []
    for deck, avg_r, r_count in decks_with_rating:
        if deck.user_id not in user_cache:
            owner = db.query(User).filter(User.id == deck.user_id).first()
            user_cache[deck.user_id] = owner.name if owner else "Unknown"
        result.append({
            "id": deck.id,
            "title": deck.title,
            "card_count": deck.card_count,
            "user_id": deck.user_id,
            "owner_name": user_cache[deck.user_id],
            "is_shared": deck.is_shared,
            "created_at": deck.created_at,
            "avg_rating": round(float(avg_r), 1),
            "rating_count": int(r_count),
            "my_rating": my_ratings.get(deck.id),
        })

    return {"total": total, "decks": result}


@router.get("/{deck_id}", response_model=FlashcardDeckDetail)
def get_flashcard_deck(
    deck_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Get a deck with all its cards. Accessible if owned or shared."""
    deck = db.query(FlashcardDeck).filter(FlashcardDeck.id == deck_id).first()
    if not deck:
        raise HTTPException(status_code=404, detail="Deck not found")
    if deck.user_id != current_user.id and not current_user.is_admin and not deck.is_shared:
        raise HTTPException(status_code=403, detail="Not your deck")
    return deck


@router.delete("/{deck_id}", status_code=204)
def delete_flashcard_deck(
    deck_id: int,
    permanent: bool = Query(False),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Soft-delete a deck (moves to trash). Use ?permanent=true to permanently delete."""
    deck = _own_deck_or_404(deck_id, current_user, db)
    if permanent:
        db.delete(deck)
    else:
        deck.deleted_at = datetime.utcnow()
    db.commit()


@router.post("/{deck_id}/restore", status_code=200)
def restore_flashcard_deck(
    deck_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Restore a soft-deleted deck from trash."""
    deck = _own_deck_or_404(deck_id, current_user, db)
    if not deck.deleted_at:
        raise HTTPException(status_code=400, detail="Deck is not in trash")
    deck.deleted_at = None
    db.commit()
    return {"id": deck.id, "title": deck.title, "restored": True}


# ── Sharing & rating ────────────────────────────────────────────────

@router.patch("/{deck_id}")
def update_deck(
    deck_id: int,
    data: FlashcardDeckUpdate,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Update deck metadata (title). Owner or admin only."""
    deck = _own_deck_or_404(deck_id, current_user, db)
    if data.title is not None:
        title = data.title.strip()
        if not title:
            raise HTTPException(status_code=400, detail="Title cannot be empty")
        deck.title = title[:300]
    db.commit()
    db.refresh(deck)
    return {"id": deck.id, "title": deck.title}


@router.put("/{deck_id}/share")
def toggle_share_deck(
    deck_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Toggle sharing on a deck. Owner or admin."""
    deck = _own_deck_or_404(deck_id, current_user, db)
    deck.is_shared = 0 if deck.is_shared else 1
    db.commit()
    return {"id": deck.id, "is_shared": deck.is_shared}


@router.delete("/{deck_id}/share", status_code=204)
def admin_unshare_deck(
    deck_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Admin: force-unshare any deck."""
    if not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Admin only")
    deck = db.query(FlashcardDeck).filter(FlashcardDeck.id == deck_id).first()
    if not deck:
        raise HTTPException(status_code=404, detail="Deck not found")
    deck.is_shared = 0
    db.commit()


@router.post("/{deck_id}/rate")
def rate_deck(
    deck_id: int,
    data: RatingRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Rate a shared deck (1-5). Updates if already rated."""
    if data.rating < 1 or data.rating > 5:
        raise HTTPException(status_code=400, detail="Rating must be 1-5")

    deck = db.query(FlashcardDeck).filter(FlashcardDeck.id == deck_id).first()
    if not deck:
        raise HTTPException(status_code=404, detail="Deck not found")
    if not deck.is_shared:
        raise HTTPException(status_code=400, detail="Deck is not shared")

    existing = db.query(FlashcardDeckRating).filter(
        FlashcardDeckRating.deck_id == deck_id,
        FlashcardDeckRating.user_id == current_user.id,
    ).first()

    if existing:
        existing.rating = data.rating
    else:
        db.add(FlashcardDeckRating(
            deck_id=deck_id,
            user_id=current_user.id,
            rating=data.rating,
        ))
    db.commit()

    # Return updated average
    from sqlalchemy import func
    avg = db.query(func.avg(FlashcardDeckRating.rating)).filter(
        FlashcardDeckRating.deck_id == deck_id
    ).scalar()
    count = db.query(FlashcardDeckRating).filter(FlashcardDeckRating.deck_id == deck_id).count()

    return {"deck_id": deck_id, "my_rating": data.rating, "avg_rating": round(float(avg), 1), "rating_count": count}


# ── Card browse endpoints ────────────────────────────────────────────

@router.get("/cards/browse")
def browse_flashcards(
    q: str | None = Query(None),
    deck_id: int | None = Query(None),
    tag_ids: str | None = Query(None, description="Comma-separated tag IDs (AND filter)"),
    limit: int = Query(50, le=200),
    offset: int = Query(0),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Browse flashcards across user's own decks."""
    # Get user's active (non-deleted) deck IDs
    own_deck_ids = [d.id for d in db.query(FlashcardDeck.id).filter(
        FlashcardDeck.user_id == current_user.id,
        FlashcardDeck.deleted_at.is_(None),
    ).all()]
    if not own_deck_ids:
        return {"total": 0, "cards": []}

    query = db.query(Flashcard).filter(Flashcard.deck_id.in_(own_deck_ids))

    if deck_id:
        query = query.filter(Flashcard.deck_id == deck_id)

    if q and q.strip():
        phrase = q.strip()
        query = query.filter(
            or_(
                Flashcard.front.ilike(f"%{phrase}%"),
                Flashcard.back.ilike(f"%{phrase}%"),
            )
        )

    # Tag filter
    if tag_ids:
        tag_id_list = [int(t.strip()) for t in tag_ids.split(",") if t.strip().isdigit()]
        if tag_id_list:
            matching_ids = list(db.execute(sa_text("""
                SELECT flashcard_id FROM flashcard_tag_links
                WHERE tag_id = ANY(:tag_ids)
                GROUP BY flashcard_id
                HAVING COUNT(DISTINCT tag_id) = :cnt
            """), {"tag_ids": tag_id_list, "cnt": len(tag_id_list)}).scalars())
            if matching_ids:
                query = query.filter(Flashcard.id.in_(matching_ids))
            else:
                return {"total": 0, "cards": []}

    total = query.count()
    cards = query.order_by(Flashcard.deck_id, Flashcard.id).offset(offset).limit(limit).all()

    deck_cache: dict[int, str] = {}
    result = []
    for card in cards:
        if card.deck_id not in deck_cache:
            deck = db.query(FlashcardDeck).filter(FlashcardDeck.id == card.deck_id).first()
            deck_cache[card.deck_id] = deck.title if deck else f"Deck {card.deck_id}"
        result.append({
            "id": card.id,
            "deck_id": card.deck_id,
            "deck_title": deck_cache[card.deck_id],
            "front": card.front,
            "back": card.back,
            "page_reference": card.page_reference,
            "image_path": card.image_path,
            "created_at": card.created_at,
        })

    return {"total": total, "cards": result}


@router.get("/cards/browse/ids")
def browse_flashcard_ids(
    q: str | None = Query(None),
    deck_id: int | None = Query(None),
    tag_ids: str | None = Query(None),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Return just IDs for matching cards (for select-all)."""
    own_deck_ids = [d.id for d in db.query(FlashcardDeck.id).filter(
        FlashcardDeck.user_id == current_user.id,
        FlashcardDeck.deleted_at.is_(None),
    ).all()]
    if not own_deck_ids:
        return []

    query = db.query(Flashcard.id).filter(Flashcard.deck_id.in_(own_deck_ids))

    if deck_id:
        query = query.filter(Flashcard.deck_id == deck_id)
    if q and q.strip():
        phrase = q.strip()
        query = query.filter(or_(
            Flashcard.front.ilike(f"%{phrase}%"),
            Flashcard.back.ilike(f"%{phrase}%"),
        ))
    if tag_ids:
        tag_id_list = [int(t.strip()) for t in tag_ids.split(",") if t.strip().isdigit()]
        if tag_id_list:
            matching_ids = list(db.execute(sa_text("""
                SELECT flashcard_id FROM flashcard_tag_links
                WHERE tag_id = ANY(:tag_ids)
                GROUP BY flashcard_id
                HAVING COUNT(DISTINCT tag_id) = :cnt
            """), {"tag_ids": tag_id_list, "cnt": len(tag_id_list)}).scalars())
            if matching_ids:
                query = query.filter(Flashcard.id.in_(matching_ids))
            else:
                return []

    return [row[0] for row in query.all()]


# ── Single card endpoints ────────────────────────────────────────────

@router.put("/cards/{card_id}")
def update_flashcard(
    card_id: int,
    data: CardEdit,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Edit a single flashcard (front, back). Owner or admin."""
    card = db.query(Flashcard).filter(Flashcard.id == card_id).first()
    if not card:
        raise HTTPException(status_code=404, detail="Card not found")
    deck = db.query(FlashcardDeck).filter(FlashcardDeck.id == card.deck_id).first()
    if deck and deck.user_id != current_user.id and not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Not your card")
    if data.front is not None:
        card.front = data.front
    if data.back is not None:
        card.back = data.back
    db.commit()
    db.refresh(card)
    return {"id": card.id, "deck_id": card.deck_id, "front": card.front, "back": card.back}


@router.delete("/cards/{card_id}", status_code=204)
def delete_flashcard(
    card_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Delete a single flashcard. Owner or admin."""
    card = db.query(Flashcard).filter(Flashcard.id == card_id).first()
    if not card:
        raise HTTPException(status_code=404, detail="Card not found")
    deck = db.query(FlashcardDeck).filter(FlashcardDeck.id == card.deck_id).first()
    if deck and deck.user_id != current_user.id and not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Not your card")
    db.delete(card)
    if deck and deck.card_count > 0:
        deck.card_count -= 1
    db.commit()
