from __future__ import annotations

import argparse
import json
import shutil
import time
from pathlib import Path
from urllib.parse import urlparse

import requests

from item_catalog import CatalogIdentity, VariantSelection
from models import QUALITY_NAMES
from official_stats import OfficialItemClient
from rules import RuleBook


def safe(value: str) -> str:
    out = []
    for ch in value:
        out.append(ch if ch.isalnum() or ch in "._-" else "_")
    return "".join(out).strip("_") or "asset"


def load_registry(pack: Path) -> dict:
    path = pack / "registry.json"
    if path.is_file():
        data = json.loads(path.read_text(encoding="utf-8"))
    else:
        data = {}
    for key in ("bases", "main_variants", "prefixes", "suffixes", "remote", "failures"):
        data.setdefault(key, {} if key != "failures" else [])
    return data


def save_registry(pack: Path, data: dict) -> None:
    pack.mkdir(parents=True, exist_ok=True)
    (pack / "registry.json").write_text(
        json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8"
    )


def copy_download(path: str, pack: Path, group: str, key: str) -> str:
    src = Path(path)
    suffix = src.suffix.lower() or ".jpg"
    dest_dir = pack / group
    dest_dir.mkdir(parents=True, exist_ok=True)
    dest = dest_dir / f"{safe(key)}{suffix}"
    if not dest.exists():
        shutil.copy2(src, dest)
    return dest.relative_to(pack).as_posix()


def fetch_selection(client, selection, pack, registry, *, want_main=False, want_prefix=False, want_suffix=False):
    stats = client.fetch(selection)
    temp = pack / "_download_cache"
    paths = client.download_images(stats, temp)
    ident = selection.identity
    if want_main and paths.get("main"):
        key = f"{ident.category}|{ident.base}|{selection.quality}|{1 if selection.legendary else 0}"
        rel = copy_download(paths["main"], pack, "main", key)
        registry["main_variants"][key] = rel
        registry["bases"].setdefault(f"{ident.category}|{ident.base}", rel)
        registry["remote"][f"main|{key}"] = stats.main_image_url
    if want_prefix and ident.prefix and paths.get("prefix"):
        sem = client.rules.prefix_semantic_key(ident.prefix)
        key = f"{ident.category}|{sem}"
        registry["prefixes"][key] = copy_download(paths["prefix"], pack, "prefix", key)
        registry["remote"][f"prefix|{key}"] = stats.prefix_image_url
    if want_suffix and ident.suffix and paths.get("suffix"):
        key = f"{ident.category}|{ident.suffix}"
        registry["suffixes"][key] = copy_download(paths["suffix"], pack, "suffix", key)
        registry["remote"][f"suffix|{key}"] = stats.suffix_image_url


def expected_counts(rules):
    return {
        "main_variants": sum(len(rules.bases(cat)) * len(QUALITY_NAMES) * 2 for cat in rules.categories()),
        "prefixes": sum(len(set(rules.prefix_semantic_key(x) for x in rules.prefixes(cat))) for cat in rules.categories()),
        "suffixes": sum(len(set(rules.suffixes(cat))) for cat in rules.categories()),
    }


def write_report(root: Path, rules, registry, failures: list[str]) -> None:
    expected = expected_counts(rules)
    lines = [
        "BW Crafting Planner — końcowy raport pakietu grafik",
        "",
        f"Główne obrazy wariantów: {len(registry['main_variants'])}/{expected['main_variants']}",
        f"Grafiki prefixów: {len(registry['prefixes'])}/{expected['prefixes']}",
        f"Grafiki suffixów: {len(registry['suffixes'])}/{expected['suffixes']}",
        "",
    ]
    for cat in rules.categories():
        prefixes = sorted(set(rules.prefix_semantic_key(x) for x in rules.prefixes(cat)))
        suffixes = sorted(set(rules.suffixes(cat)))
        missing_p = [x for x in prefixes if f"{cat}|{x}" not in registry["prefixes"]]
        missing_s = [x for x in suffixes if f"{cat}|{x}" not in registry["suffixes"]]
        lines.append(f"[{rules.category_label(cat)}]")
        lines.append("Brakujące prefixy: " + (", ".join(rules.prefix_filter_label(x) for x in missing_p) or "brak"))
        lines.append("Brakujące suffixy: " + (", ".join(rules.display(x) for x in missing_s) or "brak"))
        lines.append("")
    if failures:
        lines.append("BŁĘDY POBIERANIA:")
        lines.extend(f"- {failure}" for failure in failures)
    else:
        lines.append("Błędy pobierania: brak")
    (root / "ASSET_FETCH_REPORT.txt").write_text("\n".join(lines), encoding="utf-8")


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--delay", type=float, default=0.35, help="Przerwa między zapytaniami")
    parser.add_argument("--retry", type=int, default=2)
    parser.add_argument("--quick", action="store_true", help="Tylko po jednej grafice głównej na bazę")
    args = parser.parse_args()

    root = Path(__file__).resolve().parent
    pack = root / "assets" / "item_pack"
    rules = RuleBook(root / "data")
    registry = load_registry(pack)
    client = OfficialItemClient(rules, timeout=35.0)
    failures: list[str] = []
    completed = 0

    # First consume exact URLs found in the user's complete-page export. This is
    # faster and avoids unnecessary tester-page requests.
    direct_session = requests.Session()
    direct_session.headers.update({"User-Agent": "Mozilla/5.0 BW-Crafting-Planner/0.12"})
    for remote_key, url in list(registry.get("remote", {}).items()):
        try:
            kind, key = remote_key.split("|", 1)
        except ValueError:
            continue
        target_group = {"main": "main_variants", "base": "bases", "prefix": "prefixes", "suffix": "suffixes"}.get(kind)
        if not target_group or key in registry[target_group] or not url:
            continue
        try:
            response = direct_session.get(url, timeout=35)
            response.raise_for_status()
            suffix = Path(urlparse(url).path).suffix.lower() or ".jpg"
            temp_dir = pack / "_download_cache"
            temp_dir.mkdir(parents=True, exist_ok=True)
            temp_file = temp_dir / (safe(key) + suffix)
            temp_file.write_bytes(response.content)
            group_dir = "main" if kind in {"main", "base"} else kind
            rel = copy_download(str(temp_file), pack, group_dir, key)
            registry[target_group][key] = rel
            print(f"URL {remote_key}")
        except Exception as exc:  # noqa: BLE001
            # The official tester pass below may still discover a working URL.
            print(f"URL-ERR {remote_key}: {exc}")
    save_registry(pack, registry)

    def run(label, selection, **wants):
        nonlocal completed
        for attempt in range(args.retry + 1):
            try:
                fetch_selection(client, selection, pack, registry, **wants)
                completed += 1
                print(f"OK  [{completed}] {label}")
                save_registry(pack, registry)
                time.sleep(max(0.0, args.delay))
                return
            except Exception as exc:  # noqa: BLE001
                if attempt >= args.retry:
                    msg = f"{label}: {exc}"
                    print("ERR", msg)
                    failures.append(msg)
                else:
                    time.sleep(max(1.0, args.delay * 3))

    # Main images. Upgrades do not alter art; quality and legendary status do.
    for cat in rules.categories():
        for base in rules.bases(cat):
            qualities = ["Zwykły"] if args.quick else QUALITY_NAMES
            legends = [False] if args.quick else [False, True]
            for quality in qualities:
                for legendary in legends:
                    key = f"{cat}|{base}|{quality}|{1 if legendary else 0}"
                    if key in registry["main_variants"]:
                        continue
                    selection = VariantSelection(
                        CatalogIdentity(cat, "", base, ""),
                        quality=quality, upgrade=0, legendary=legendary, player_level=80,
                    )
                    run(f"MAIN {key}", selection, want_main=True)

    # One representative base per category is enough to reveal category-specific affix art.
    for cat in rules.categories():
        bases = rules.bases(cat)
        if not bases:
            continue
        base = bases[0]
        seen_prefix: set[str] = set()
        for prefix in rules.prefixes(cat):
            semantic = rules.prefix_semantic_key(prefix)
            if semantic in seen_prefix:
                continue
            seen_prefix.add(semantic)
            key = f"{cat}|{semantic}"
            if key in registry["prefixes"]:
                continue
            selection = VariantSelection(
                CatalogIdentity(cat, prefix, base, ""),
                quality="Zwykły", upgrade=0, legendary=False, player_level=80,
            )
            run(f"PREFIX {key}", selection, want_prefix=True)
        for suffix in rules.suffixes(cat):
            key = f"{cat}|{suffix}"
            if key in registry["suffixes"]:
                continue
            selection = VariantSelection(
                CatalogIdentity(cat, "", base, suffix),
                quality="Zwykły", upgrade=0, legendary=False, player_level=80,
            )
            run(f"SUFFIX {key}", selection, want_suffix=True)

    registry["failures"] = failures
    save_registry(pack, registry)
    write_report(root, rules, registry, failures)
    expected = expected_counts(rules)
    complete = (
        len(registry["main_variants"]) >= expected["main_variants"]
        and len(registry["prefixes"]) >= expected["prefixes"]
        and len(registry["suffixes"]) >= expected["suffixes"]
    )
    marker = pack / "COMPLETE"
    if complete:
        marker.write_text("complete\n", encoding="utf-8")
        print("Pakiet kompletny.")
    elif marker.exists():
        marker.unlink()
    print(f"Raport: {root / 'ASSET_FETCH_REPORT.txt'}")
    # Partial packs remain useful and should not block EXE creation.
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
