from __future__ import annotations

from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from pathlib import Path
import csv
import os
from typing import Any, Dict, List, Optional, Tuple

from ..app import templates
from ..services.orchestrator import tags_for_commander
from ..services.summary_utils import format_theme_label, format_theme_list, summary_ctx


router = APIRouter(prefix="/decks")


def _deck_dir() -> Path:
    # Prefer explicit env var if provided, else default to ./deck_files
    p = os.getenv("DECK_EXPORTS")
    if p:
        return Path(p).resolve()
    return (Path.cwd() / "deck_files").resolve()


def _list_decks() -> list[dict]:
    d = _deck_dir()
    try:
        d.mkdir(parents=True, exist_ok=True)
    except Exception:
        pass
    items: list[dict] = []
    # Prefer CSV entries and pair with matching TXT if present
    for p in sorted(d.glob("*.csv"), key=lambda x: x.stat().st_mtime, reverse=True):
        meta = {"name": p.name, "path": str(p), "mtime": p.stat().st_mtime}
        stem = p.stem
        txt = p.with_suffix('.txt')
        if txt.exists():
            meta["txt_name"] = txt.name
            meta["txt_path"] = str(txt)
        # Prefer sidecar summary meta if present
        sidecar = p.with_suffix('.summary.json')
        if sidecar.exists():
            try:
                import json as _json
                payload = _json.loads(sidecar.read_text(encoding='utf-8'))
                _m = payload.get('meta', {}) if isinstance(payload, dict) else {}
                meta["commander"] = _m.get('commander') or meta.get("commander")
                meta["tags"] = _m.get('tags') or meta.get("tags") or []
                if _m.get('name'):
                    meta["display"] = _m.get('name')
            except Exception:
                pass
        # Fallback to parsing commander/themes from filename convention Commander_Themes_YYYYMMDD
        if not meta.get("commander"):
            parts = stem.split('_')
            if len(parts) >= 3:
                meta["commander"] = parts[0]
                meta["tags"] = parts[1:-1]
            else:
                meta["commander"] = stem
                meta["tags"] = []
        items.append(meta)
    return items


def _safe_within(base: Path, target: Path) -> bool:
    try:
        base_r = base.resolve()
        targ_r = target.resolve()
        return (base_r == targ_r) or (base_r in targ_r.parents)
    except Exception:
        return False


def _read_csv_summary(csv_path: Path) -> Tuple[dict, Dict[str, int], Dict[str, int], Dict[str, List[dict]]]:
    """Parse CSV export to reconstruct minimal summary pieces.

    Returns: (meta, type_counts, curve_counts, type_cards)
      meta: { 'commander': str, 'colors': [..] }
    """
    headers = []
    type_counts: Dict[str, int] = {}
    type_cards: Dict[str, List[dict]] = {}
    curve_bins = ['0','1','2','3','4','5','6+']
    curve_counts: Dict[str, int] = {b: 0 for b in curve_bins}
    curve_cards: Dict[str, List[dict]] = {b: [] for b in curve_bins}
    meta: dict = {"commander": "", "colors": []}
    commander_seen = False
    # Infer commander from filename stem (pattern Commander_Themes_YYYYMMDD)
    stem_parts = csv_path.stem.split('_')
    inferred_commander = stem_parts[0] if stem_parts else ''

    def classify_mv(raw) -> str:
        try:
            v = float(raw)
        except Exception:
            v = 0.0
        return '6+' if v >= 6 else str(int(v))

    try:
        with csv_path.open('r', encoding='utf-8') as f:
            reader = csv.reader(f)
            headers = next(reader, [])
            # Expected columns include: Name, Count, Type, ManaCost, ManaValue, Colors, Power, Toughness, Role, ..., Tags, Text, Owned
            name_idx = headers.index('Name') if 'Name' in headers else 0
            count_idx = headers.index('Count') if 'Count' in headers else 1
            type_idx = headers.index('Type') if 'Type' in headers else 2
            mv_idx = headers.index('ManaValue') if 'ManaValue' in headers else (headers.index('Mana Value') if 'Mana Value' in headers else -1)
            role_idx = headers.index('Role') if 'Role' in headers else -1
            tags_idx = headers.index('Tags') if 'Tags' in headers else -1
            colors_idx = headers.index('Colors') if 'Colors' in headers else -1

            for row in reader:
                if not row:
                    continue
                try:
                    name = row[name_idx]
                except Exception:
                    continue
                try:
                    cnt = int(float(row[count_idx])) if row[count_idx] else 1
                except Exception:
                    cnt = 1
                type_line = row[type_idx] if type_idx >= 0 and type_idx < len(row) else ''
                role = (row[role_idx] if role_idx >= 0 and role_idx < len(row) else '')
                tags = (row[tags_idx] if tags_idx >= 0 and tags_idx < len(row) else '')
                tags_list = [t.strip() for t in tags.split(';') if t.strip()]

                # Commander detection: prefer filename inference; else best-effort via type line containing 'Commander'
                is_commander = (inferred_commander and name == inferred_commander)
                if not is_commander:
                    is_commander = isinstance(type_line, str) and ('commander' in type_line.lower())
                if is_commander and not commander_seen:
                    meta['commander'] = name
                    commander_seen = True

                # Map type_line to broad category
                tl = (type_line or '').lower()
                if 'battle' in tl:
                    cat = 'Battle'
                elif 'planeswalker' in tl:
                    cat = 'Planeswalker'
                elif 'creature' in tl:
                    cat = 'Creature'
                elif 'instant' in tl:
                    cat = 'Instant'
                elif 'sorcery' in tl:
                    cat = 'Sorcery'
                elif 'artifact' in tl:
                    cat = 'Artifact'
                elif 'enchantment' in tl:
                    cat = 'Enchantment'
                elif 'land' in tl:
                    cat = 'Land'
                else:
                    cat = 'Other'

                # Type counts/cards (exclude commander entry from distribution)
                if not is_commander:
                    type_counts[cat] = type_counts.get(cat, 0) + cnt
                    # M5: Extract metadata tags column if present
                    metadata_tags_raw = ''
                    metadata_idx = headers.index('MetadataTags') if 'MetadataTags' in headers else -1
                    if metadata_idx >= 0 and metadata_idx < len(row):
                        metadata_tags_raw = row[metadata_idx] or ''
                    metadata_tags_list = [t.strip() for t in metadata_tags_raw.split(';') if t.strip()]
                    type_cards.setdefault(cat, []).append({
                        'name': name,
                        'count': cnt,
                        'role': role,
                        'tags': tags_list,
                        'metadata_tags': metadata_tags_list,  # M5: Include metadata tags
                    })

                # Curve
                if mv_idx >= 0 and mv_idx < len(row):
                    bucket = classify_mv(row[mv_idx])
                    if bucket not in curve_counts:
                        bucket = '6+'
                    curve_counts[bucket] += cnt
                    curve_cards[bucket].append({'name': name, 'count': cnt})

                # Colors (from Colors col for commander/overall)
                if is_commander and colors_idx >= 0 and colors_idx < len(row):
                    cid = row[colors_idx] or ''
                    if isinstance(cid, str):
                        meta['colors'] = list(cid)
    except Exception:
        pass

    # Precedence ordering
    precedence_order = [
        'Battle', 'Planeswalker', 'Creature', 'Instant', 'Sorcery', 'Artifact', 'Enchantment', 'Land', 'Other'
    ]
    prec_index = {k: i for i, k in enumerate(precedence_order)}
    type_order = sorted(type_counts.keys(), key=lambda k: prec_index.get(k, 999))

    summary = {
        'type_breakdown': {
            'counts': type_counts,
            'order': type_order,
            'cards': type_cards,
            'total': sum(type_counts.values()),
        },
        'pip_distribution': {
            # Not recoverable from CSV without mana symbols; leave zeros
            'counts': {c: 0 for c in ('W','U','B','R','G')},
            'weights': {c: 0 for c in ('W','U','B','R','G')},
        },
        'mana_generation': {
            # Not recoverable from CSV alone
            'W': 0, 'U': 0, 'B': 0, 'R': 0, 'G': 0, 'total_sources': 0,
        },
        'mana_curve': {
            **curve_counts,
            'total_spells': sum(curve_counts.values()),
            'cards': curve_cards,
        },
        'colors': meta.get('colors', []),
    }
    return summary, type_counts, curve_counts, type_cards


def _read_deck_counts(csv_path: Path) -> Dict[str, int]:
    """Read a CSV deck export and return a mapping of card name -> total count.

    Falls back to zero on parse issues; ignores header case and missing columns.
    """
    counts: Dict[str, int] = {}
    try:
        with csv_path.open('r', encoding='utf-8') as f:
            reader = csv.reader(f)
            headers = next(reader, [])
            name_idx = headers.index('Name') if 'Name' in headers else 0
            count_idx = headers.index('Count') if 'Count' in headers else 1
            for row in reader:
                if not row:
                    continue
                try:
                    name = row[name_idx]
                except Exception:
                    continue
                try:
                    cnt = int(float(row[count_idx])) if row[count_idx] else 1
                except Exception:
                    cnt = 1
                name = str(name).strip()
                if not name:
                    continue
                counts[name] = counts.get(name, 0) + cnt
    except Exception:
        pass
    return counts


@router.get("/", response_class=HTMLResponse)
async def decks_index(request: Request) -> HTMLResponse:
    items = _list_decks()
    return templates.TemplateResponse("decks/index.html", {"request": request, "items": items})


@router.get("/view", response_class=HTMLResponse)
async def decks_view(request: Request, name: str) -> HTMLResponse:
    base = _deck_dir()
    p = (base / name).resolve()
    if not _safe_within(base, p) or not (p.exists() and p.is_file() and p.suffix.lower() == ".csv"):
        return templates.TemplateResponse("decks/index.html", {"request": request, "items": _list_decks(), "error": "Deck not found."})

    # Try to load sidecar summary JSON first
    summary = None
    commander_name = ''
    tags: List[str] = []
    meta_info: Dict[str, Any] = {}
    sidecar = p.with_suffix('.summary.json')
    if sidecar.exists():
        try:
            import json as _json
            payload = _json.loads(sidecar.read_text(encoding='utf-8'))
            if isinstance(payload, dict):
                summary = payload.get('summary')
                meta = payload.get('meta', {})
                if isinstance(meta, dict):
                    meta_info = meta
                    commander_name = meta.get('commander') or ''
                    _tags = meta.get('tags') or []
                    if isinstance(_tags, list):
                        tags = [str(t) for t in _tags]
                    display_name = meta.get('name') or ''
        except Exception:
            summary = None
            display_name = ''
    if not summary:
        # Reconstruct minimal summary from CSV
        summary, _tc, _cc, _tcs = _read_csv_summary(p)
        display_name = ''
    stem = p.stem
    txt_path = p.with_suffix('.txt')
    # If missing still, infer from filename stem
    if not commander_name:
        parts = stem.split('_')
        commander_name = parts[0] if parts else ''

    ctx = {
        "request": request,
        "name": p.name,
        "csv_path": str(p),
        "txt_path": str(txt_path) if txt_path.exists() else None,
        "summary": summary,
        "commander": commander_name,
        "tags": tags,
        "display_name": display_name,
    }
    ctx.update(summary_ctx(summary=summary, commander=commander_name, tags=tags, meta=meta_info))

    def _extend_sources(values: list[Any], candidate: Any) -> None:
        if isinstance(candidate, list):
            values.extend(candidate)
        elif isinstance(candidate, tuple):
            values.extend(list(candidate))
        elif isinstance(candidate, str):
            values.append(candidate)

    deck_theme_sources: list[Any] = list(ctx.get("synergies") or tags or [])
    if isinstance(meta_info, dict):
        for key in (
            "display_themes",
            "resolved_themes",
            "auto_filled_themes",
            "random_display_themes",
            "random_resolved_themes",
            "random_auto_filled_themes",
            "primary_theme",
            "secondary_theme",
            "tertiary_theme",
        ):
            _extend_sources(deck_theme_sources, meta_info.get(key))
    deck_theme_tags = format_theme_list(deck_theme_sources)

    commander_theme_sources: list[Any] = []
    if isinstance(meta_info, dict):
        for key in (
            "commander_tags",
            "commander_theme_tags",
            "commander_themes",
            "commander_tag_list",
            "primary_commander_theme",
            "secondary_commander_theme",
        ):
            _extend_sources(commander_theme_sources, meta_info.get(key))
        commander_meta = meta_info.get("commander", {})
        if isinstance(commander_meta, dict):
            _extend_sources(commander_theme_sources, commander_meta.get("tags"))
            _extend_sources(commander_theme_sources, commander_meta.get("themes"))
    commander_theme_tags = format_theme_list(commander_theme_sources)
    if not commander_theme_tags and commander_name:
        commander_theme_tags = format_theme_list(tags_for_commander(commander_name))

    combined_tags: list[str] = []
    combined_seen: set[str] = set()
    for collection in (commander_theme_tags, deck_theme_tags):
        for label in collection:
            key = label.casefold()
            if key in combined_seen:
                continue
            combined_seen.add(key)
            combined_tags.append(label)

    overlap_tags: list[str] = []
    overlap_seen: set[str] = set()
    combined_keys = {label.casefold() for label in combined_tags}
    for label in deck_theme_tags:
        key = label.casefold()
        if key in combined_keys and key not in overlap_seen:
            overlap_tags.append(label)
            overlap_seen.add(key)

    commander_tag_slugs = []
    slug_seen: set[str] = set()
    for label in combined_tags:
        slug = " ".join(str(label or "").strip().lower().split())
        if not slug or slug in slug_seen:
            continue
        slug_seen.add(slug)
        commander_tag_slugs.append(slug)

    reason_bits: list[str] = []
    if deck_theme_tags:
        reason_bits.append("Deck themes: " + ", ".join(deck_theme_tags))
    if commander_theme_tags:
        reason_bits.append("Commander tags: " + ", ".join(commander_theme_tags))
    commander_reason_text = "; ".join(reason_bits)

    ctx.update(
        {
            "deck_theme_tags": deck_theme_tags,
            "commander_theme_tags": commander_theme_tags,
            "commander_combined_tags": combined_tags,
            "commander_tag_slugs": commander_tag_slugs,
            "commander_reason_text": commander_reason_text,
            "commander_overlap_tags": overlap_tags,
            "commander_role_label": format_theme_label("Commander"),
        }
    )
    return templates.TemplateResponse("decks/view.html", ctx)


@router.get("/compare", response_class=HTMLResponse)
async def decks_compare(request: Request, A: Optional[str] = None, B: Optional[str] = None) -> HTMLResponse:
    """Compare two finished deck CSVs and show diffs.

    Query params:
      - A: filename of first deck (e.g., Alena_..._20250827.csv)
      - B: filename of second deck
    """
    base = _deck_dir()
    items = _list_decks()
    # Build select options with friendly display labels
    options: List[Dict[str, str]] = []
    for it in items:
        label = it.get("display") or it.get("commander") or it.get("name")
        # Include mtime for "Latest two" selection refinement
        mt = it.get("mtime", 0)
        try:
            mt_val = str(int(mt))
        except Exception:
            mt_val = "0"
        options.append({"name": it.get("name"), "label": label, "mtime": mt_val})  # type: ignore[arg-type]

    diffs = None
    metaA: Dict[str, str] = {}
    metaB: Dict[str, str] = {}
    if A and B:
        pA = (base / A)
        pB = (base / B)
        if _safe_within(base, pA) and _safe_within(base, pB) and pA.exists() and pB.exists():
            ca = _read_deck_counts(pA)
            cb = _read_deck_counts(pB)
            setA = set(ca.keys())
            setB = set(cb.keys())
            onlyA = sorted(list(setA - setB))
            onlyB = sorted(list(setB - setA))
            changed: List[Tuple[str, int, int]] = []
            for n in sorted(setA & setB):
                if ca.get(n, 0) != cb.get(n, 0):
                    changed.append((n, ca.get(n, 0), cb.get(n, 0)))
            # Side meta (commander/name/tags) if available
            def _meta_for(path: Path) -> Dict[str, str]:
                out: Dict[str, str] = {"filename": path.name}
                sc = path.with_suffix('.summary.json')
                try:
                    if sc.exists():
                        import json as _json
                        payload = _json.loads(sc.read_text(encoding='utf-8'))
                        if isinstance(payload, dict):
                            m = payload.get('meta', {}) or {}
                            out["display"] = (m.get('name') or '')
                            out["commander"] = (m.get('commander') or '')
                            out["tags"] = ', '.join(m.get('tags') or [])
                except Exception:
                    pass
                if not out.get("commander"):
                    parts = path.stem.split('_')
                    if parts:
                        out["commander"] = parts[0]
                return out
            metaA = _meta_for(pA)
            metaB = _meta_for(pB)
            diffs = {
                "onlyA": onlyA,
                "onlyB": onlyB,
                "changed": changed,
                "A": A,
                "B": B,
            }

    return templates.TemplateResponse(
        "decks/compare.html",
        {
            "request": request,
            "options": options,
            "A": A or "",
            "B": B or "",
            "diffs": diffs,
            "metaA": metaA,
            "metaB": metaB,
        },
    )
