"""Multi-copy archetype routes for deck building.

Handles multi-copy package detection, selection, and integration with the deck builder.
Multi-copy archetypes allow multiple copies of specific cards (e.g., Hare Apparent, Dragon's Approach).

Routes:
    GET /multicopy/check - Check if commander/tags suggest multi-copy archetype  
    POST /multicopy/save - Save or skip multi-copy selection
    GET /new/multicopy - Get multi-copy suggestions for New Deck modal (inline)

Created: 2026-02-20
Roadmap: R9 M1 Phase 2
"""

from __future__ import annotations

from fastapi import APIRouter, Request, Form, Query
from fastapi.responses import HTMLResponse
from html import escape as _esc

from deck_builder.builder import DeckBuilder
from deck_builder import builder_utils as bu, builder_constants as bc
from ..app import templates
from ..services.tasks import get_session, new_sid
from ..services import orchestrator as orch
from ..services.build_utils import owned_names as owned_names_helper

router = APIRouter()


def _rebuild_ctx_with_multicopy(sess: dict) -> None:
    """Rebuild the staged context so Multi-Copy runs first, avoiding overfill.

    This ensures the added cards are accounted for before lands and later phases,
    which keeps totals near targets and shows the multi-copy additions ahead of basics.
    
    Args:
        sess: Session dictionary containing build state
    """
    try:
        if not sess or not sess.get("commander"):
            return
        # Build fresh ctx with the same options, threading multi_copy explicitly
        opts = orch.bracket_options()
        default_bracket = (opts[0]["level"] if opts else 1)
        bracket_val = sess.get("bracket")
        try:
            safe_bracket = int(bracket_val) if bracket_val is not None else default_bracket
        except Exception:
            safe_bracket = int(default_bracket)
        ideals_val = sess.get("ideals") or orch.ideal_defaults()
        use_owned = bool(sess.get("use_owned_only"))
        prefer = bool(sess.get("prefer_owned"))
        owned_names = owned_names_helper() if (use_owned or prefer) else None
        locks = list(sess.get("locks", []))
        sess["build_ctx"] = orch.start_build_ctx(
            commander=sess.get("commander"),
            tags=sess.get("tags", []),
            bracket=safe_bracket,
            ideals=ideals_val,
            tag_mode=sess.get("tag_mode", "AND"),
            use_owned_only=use_owned,
            prefer_owned=prefer,
            owned_names=owned_names,
            locks=locks,
            custom_export_base=sess.get("custom_export_base"),
            multi_copy=sess.get("multi_copy"),
            prefer_combos=bool(sess.get("prefer_combos")),
            combo_target_count=int(sess.get("combo_target_count", 2)),
            combo_balance=str(sess.get("combo_balance", "mix")),
            swap_mdfc_basics=bool(sess.get("swap_mdfc_basics")),
        )
    except Exception:
        # If rebuild fails (e.g., commander not found in test), fall back to injecting
        # a minimal Multi-Copy stage on the existing builder so the UI can render additions.
        try:
            ctx = sess.get("build_ctx")
            if not isinstance(ctx, dict):
                return
            b = ctx.get("builder")
            if b is None:
                return
            # Thread selection onto the builder; runner will be resilient without full DFs
            try:
                setattr(b, "_web_multi_copy", sess.get("multi_copy") or None)
            except Exception:
                pass
            # Ensure minimal structures exist
            try:
                if not isinstance(getattr(b, "card_library", None), dict):
                    b.card_library = {}
            except Exception:
                pass
            try:
                if not isinstance(getattr(b, "ideal_counts", None), dict):
                    b.ideal_counts = {}
            except Exception:
                pass
            # Inject a single Multi-Copy stage
            ctx["stages"] = [{"key": "multi_copy", "label": "Multi-Copy Package", "runner_name": "__add_multi_copy__"}]
            ctx["idx"] = 0
            ctx["last_visible_idx"] = 0
        except Exception:
            # Leave existing context untouched on unexpected failure
            pass


@router.get("/multicopy/check", response_class=HTMLResponse)
async def multicopy_check(request: Request) -> HTMLResponse:
    """If current commander/tags suggest a multi-copy archetype, render a choose-one modal.

    Returns empty content when not applicable to avoid flashing a modal unnecessarily.
    
    Args:
        request: FastAPI request object
        
    Returns:
        HTMLResponse with multi-copy modal or empty string
    """
    sid = request.cookies.get("sid") or new_sid()
    sess = get_session(sid)
    commander = str(sess.get("commander") or "").strip()
    tags = list(sess.get("tags") or [])
    if not commander:
        return HTMLResponse("")
    # Avoid re-prompting repeatedly for the same selection context
    key = commander + "||" + ",".join(sorted([str(t).strip().lower() for t in tags if str(t).strip()]))
    seen = set(sess.get("mc_seen_keys", []) or [])
    if key in seen:
        return HTMLResponse("")
    # Build a light DeckBuilder seeded with commander + tags (no heavy data load required)
    try:
        tmp = DeckBuilder(output_func=lambda *_: None, input_func=lambda *_: "", headless=True)
        df = tmp.load_commander_data()
        row = df[df["name"].astype(str) == commander]
        if row.empty:
            return HTMLResponse("")
        tmp._apply_commander_selection(row.iloc[0])
        tmp.selected_tags = list(tags or [])
        try:
            tmp.primary_tag = tmp.selected_tags[0] if len(tmp.selected_tags) > 0 else None
            tmp.secondary_tag = tmp.selected_tags[1] if len(tmp.selected_tags) > 1 else None
            tmp.tertiary_tag = tmp.selected_tags[2] if len(tmp.selected_tags) > 2 else None
        except Exception:
            pass
        # Establish color identity from the selected commander
        try:
            tmp.determine_color_identity()
        except Exception:
            pass
        # Detect viable archetypes
        results = bu.detect_viable_multi_copy_archetypes(tmp) or []
        # R13: Filter out archetypes whose card is in the must-exclude list
        exclude_names = {str(c).strip().lower() for c in (sess.get("exclude_cards") or [])}
        if exclude_names:
            results = [r for r in results if str(r.get("name", "")).strip().lower() not in exclude_names]
        if not results:
            # Remember this key to avoid re-checking until tags/commander change
            try:
                seen.add(key)
                sess["mc_seen_keys"] = list(seen)
            except Exception:
                pass
            return HTMLResponse("")
        # Render modal template with top N (cap small for UX)
        items = results[:5]
        ctx = {
            "request": request,
            "items": items,
            "commander": commander,
            "tags": tags,
        }
        return templates.TemplateResponse("build/_multi_copy_modal.html", ctx)
    except Exception:
        return HTMLResponse("")


@router.post("/multicopy/save", response_class=HTMLResponse)
async def multicopy_save(
    request: Request,
    choice_id: str = Form(None),
    count: int = Form(None),
    thrumming: str | None = Form(None),
    skip: str | None = Form(None),
) -> HTMLResponse:
    """Persist user selection (or skip) for multi-copy archetype in session and close modal.

    Returns a tiny confirmation chip via OOB swap (optional) and removes the modal.
    
    Args:
        request: FastAPI request object
        choice_id: Multi-copy archetype ID (e.g., 'hare_apparent')
        count: Number of copies to include
        thrumming: Whether to include Thrumming Stone
        skip: Whether to skip multi-copy for this build
        
    Returns:
        HTMLResponse with confirmation chip (OOB swap)
    """
    sid = request.cookies.get("sid") or new_sid()
    sess = get_session(sid)
    commander = str(sess.get("commander") or "").strip()
    tags = list(sess.get("tags") or [])
    key = commander + "||" + ",".join(sorted([str(t).strip().lower() for t in tags if str(t).strip()]))
    # Update seen set to avoid re-prompt next load
    seen = set(sess.get("mc_seen_keys", []) or [])
    seen.add(key)
    sess["mc_seen_keys"] = list(seen)
    # Handle skip explicitly
    if skip and str(skip).strip() in ("1","true","on","yes"):
        # Clear any prior choice for this run
        try:
            if sess.get("multi_copy"):
                del sess["multi_copy"]
            if sess.get("mc_applied_key"):
                del sess["mc_applied_key"]
        except Exception:
            pass
        # Return nothing (modal will be removed client-side)
        # Also emit an OOB chip indicating skip
        chip = (
            '<div id="last-action" hx-swap-oob="true">'
            '<span class="chip" title="Click to dismiss">Dismissed multi-copy suggestions</span>'
            '</div>'
        )
        return HTMLResponse(chip)
    # Persist selection when provided
    payload = None
    try:
        meta = bc.MULTI_COPY_ARCHETYPES.get(str(choice_id), {})
        name = meta.get("name") or str(choice_id)
        printed_cap = meta.get("printed_cap")
        # Coerce count with bounds: default -> rec_window[0], cap by printed_cap when present
        if count is None:
            count = int(meta.get("default_count", 25))
        try:
            count = int(count)
        except Exception:
            count = int(meta.get("default_count", 25))
        if isinstance(printed_cap, int) and printed_cap > 0:
            count = max(1, min(printed_cap, count))
        payload = {
            "id": str(choice_id),
            "name": name,
            "count": int(count),
            "thrumming": True if (thrumming and str(thrumming).strip() in ("1","true","on","yes")) else False,
        }
        sess["multi_copy"] = payload
        # Mark as not yet applied so the next build start/continue can account for it once
        try:
            if sess.get("mc_applied_key"):
                del sess["mc_applied_key"]
        except Exception:
            pass
        # If there's an active build context, rebuild it so Multi-Copy runs first
        if sess.get("build_ctx"):
            _rebuild_ctx_with_multicopy(sess)
    except Exception:
        payload = None
    # Return OOB chip summarizing the selection
    if payload:
        chip = (
            '<div id="last-action" hx-swap-oob="true">'
            f'<span class="chip" title="Click to dismiss">Selected multi-copy: '
            f"<strong>{_esc(payload.get('name',''))}</strong> x{int(payload.get('count',0))}"
            f"{' + Thrumming Stone' if payload.get('thrumming') else ''}</span>"
            '</div>'
        )
    else:
        chip = (
            '<div id="last-action" hx-swap-oob="true">'
            '<span class="chip" title="Click to dismiss">Saved</span>'
            '</div>'
        )
    return HTMLResponse(chip)


@router.get("/new/multicopy", response_class=HTMLResponse)
async def build_new_multicopy(
    request: Request,
    commander: str = Query(""),
    primary_tag: str | None = Query(None),
    secondary_tag: str | None = Query(None),
    tertiary_tag: str | None = Query(None),
    tag_mode: str | None = Query("AND"),
) -> HTMLResponse:
    """Return multi-copy suggestions for the New Deck modal based on commander + selected tags.

    This does not mutate the session; it simply renders a form snippet that posts with the main modal.
    
    Args:
        request: FastAPI request object
        commander: Commander name
        primary_tag: Primary theme tag
        secondary_tag: Secondary theme tag  
        tertiary_tag: Tertiary theme tag
        tag_mode: Tag matching mode (AND/OR)
        
    Returns:
        HTMLResponse with multi-copy suggestions or empty string
    """
    name = (commander or "").strip()
    if not name:
        return HTMLResponse("")
    try:
        tmp = DeckBuilder(output_func=lambda *_: None, input_func=lambda *_: "", headless=True)
        df = tmp.load_commander_data()
        row = df[df["name"].astype(str) == name]
        if row.empty:
            return HTMLResponse("")
        tmp._apply_commander_selection(row.iloc[0])
        tags = [t for t in [primary_tag, secondary_tag, tertiary_tag] if t]
        tmp.selected_tags = list(tags or [])
        try:
            tmp.primary_tag = tmp.selected_tags[0] if len(tmp.selected_tags) > 0 else None
            tmp.secondary_tag = tmp.selected_tags[1] if len(tmp.selected_tags) > 1 else None
            tmp.tertiary_tag = tmp.selected_tags[2] if len(tmp.selected_tags) > 2 else None
        except Exception:
            pass
        try:
            tmp.determine_color_identity()
        except Exception:
            pass
        results = bu.detect_viable_multi_copy_archetypes(tmp) or []
        # For the New Deck modal, only show suggestions where the matched tags intersect
        # the explicitly selected tags (ignore commander-default themes).
        sel_tags = {str(t).strip().lower() for t in (tags or []) if str(t).strip()}
        def _matched_reason_tags(item: dict) -> set[str]:
            out = set()
            try:
                for r in item.get('reasons', []) or []:
                    if not isinstance(r, str):
                        continue
                    rl = r.strip().lower()
                    if rl.startswith('tags:'):
                        body = rl.split('tags:', 1)[1].strip()
                        parts = [p.strip() for p in body.split(',') if p.strip()]
                        out.update(parts)
            except Exception:
                return set()
            return out
        if sel_tags:
            results = [it for it in results if (_matched_reason_tags(it) & sel_tags)]
        else:
            # If no selected tags, do not show any multi-copy suggestions in the modal
            results = []
        if not results:
            return HTMLResponse("")
        items = results[:5]
        ctx = {"request": request, "items": items}
        return templates.TemplateResponse("build/_new_deck_multicopy.html", ctx)
    except Exception:
        return HTMLResponse("")
