#!/usr/bin/env python3
"""
rename_logos_by_path.py

Rename logo image basenames under a logos tree to path-prefixed names and update config.xml records.

- Dry-run by default (set dry_run = False to apply).
- Backups for renamed images and edited config.xml are stored under rename_backups/.
- Produces rename_report.csv with details.

Author: assistant (adapted to user's FM logos layout)
"""

from pathlib import Path
import shutil
import re
import csv
import xml.etree.ElementTree as ET

# ----------------- USER CONFIG -----------------
LOGOS_ROOT = Path("./")  # path to your logos folder
BACKUP_ROOT = Path("rename_backups")
REPORT_CSV = "rename_report.csv"
IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".tga", ".dds", ".bmp", ".webp"}
DRY_RUN = False  # Set to False to apply changes
# Map top-level folder names to short tokens (tweak if you prefer different tokens)
TOP_TOKEN = {
    "clubs": "clubs",
    "competitions": "comp",
    "confederations": "conf",
    "default": "default",
    "media": "media",
    "nations": "nat",
}
# tokens for intermediate folder names
SHORT_TOKEN = {
    "normal": "normal",
    "small": "small",
    "club": "club",
    "competitions": "comp",
    "competitions": "comp",
    "continents": "continents",
    "competitions": "comp",
    "continents": "cont",
    # Add or edit if your folder names differ
}
# ------------------------------------------------

if not LOGOS_ROOT.exists():
    raise SystemExit(f"Logos root not found: {LOGOS_ROOT.resolve()}")

BACKUP_ROOT.mkdir(exist_ok=True)

# Helper: produce safe token for a path part
def token_for(part: str):
    p = re.sub(r'[^A-Za-z0-9]+', '_', part.strip().lower())
    return SHORT_TOKEN.get(p, p)

# Build list of target directories to process:
# We process any directory that contains a config.xml (as you said config.xml is in each Normal/Small)
target_dirs = [p for p in LOGOS_ROOT.rglob("config.xml")]

# Prepare records
rename_entries = []  # dicts: old_path, new_path, old_basename, new_basename, status
basename_map = {}  # mapping old_basename -> new_basename (only within this logos tree; keys may repeat across dirs)

# Pre-reserve global set of basenames to avoid new collisions (collect all current basenames)
global_basename_set = set(p.name for p in LOGOS_ROOT.rglob("*") if p.is_file())

# Process each config.xml directory
for cfg_path in target_dirs:
    cfg_dir = cfg_path.parent  # directory that contains the config.xml and images
    # compute relative path under logos root to build tokens
    try:
        rel = cfg_dir.relative_to(LOGOS_ROOT)
    except Exception:
        rel = cfg_dir
    rel_parts = [p.lower() for p in rel.parts]  # e.g., ['confederations','normal','normal']
    # Build prefix tokens: top-level category + the rest
    if len(rel_parts) >= 1:
        top = rel_parts[0]
        top_token = TOP_TOKEN.get(top, top)
    else:
        top_token = "misc"
    path_tokens = [top_token] + [token_for(p) for p in rel_parts[1:]]
    # For each image file in this directory (only immediate files, not subdirs)
    for img in sorted(cfg_dir.iterdir()):
        if not img.is_file():
            continue
        if img.suffix.lower() not in IMAGE_EXTS:
            continue
        old_basename = img.name  # e.g., "5.png"
        old_stem = img.stem      # e.g., "5"
        # Build new basename using tokens and original stem (no extension in config's "from")
        new_stem_base = "_".join([t for t in path_tokens if t]) + "_" + old_stem
        # normalize
        new_stem_base = re.sub(r'[^A-Za-z0-9_.-]+', '_', new_stem_base)
        # ensure new basename is unique globally (avoid colliding with existing basenames)
        candidate = new_stem_base + img.suffix.lower()
        i = 1
        while candidate in global_basename_set:
            candidate = f"{new_stem_base}_{i}{img.suffix.lower()}"
            i += 1
        new_basename = candidate
        new_stem = Path(new_basename).stem  # for setting in config.xml 'from'
        # record mapping
        basename_map.setdefault(str(cfg_dir), {})[old_basename] = new_basename
        # reserve new basename globally
        global_basename_set.add(new_basename)
        # add to rename_entries
        rename_entries.append({
            "cfg_dir": str(cfg_dir),
            "old_path": str(img),
            "new_path": str(img.parent / new_basename),
            "old_basename": old_basename,
            "new_basename": new_basename,
            "old_stem": old_stem,
            "new_stem": new_stem,
            "status": "PENDING"
        })

# Make backups for all files we would change (images and config.xmls)
for entry in rename_entries:
    img_path = Path(entry["old_path"])
    rel_to_root = img_path.relative_to(LOGOS_ROOT)
    backup_dest = BACKUP_ROOT / rel_to_root
    backup_dest.parent.mkdir(parents=True, exist_ok=True)
    if img_path.exists():
        shutil.copy2(img_path, backup_dest)

# Also backup all config.xml files that will be edited
cfg_dirs = sorted({Path(e["cfg_dir"]) for e in rename_entries})
for cfg_dir in cfg_dirs:
    cfg_file = cfg_dir / "config.xml"
    if cfg_file.exists():
        backup_cfg = BACKUP_ROOT / cfg_file.relative_to(LOGOS_ROOT)
        backup_cfg.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(cfg_file, backup_cfg)

# Write pre-run report (dry-run)
with open(REPORT_CSV, "w", newline="", encoding="utf-8") as csvf:
    writer = csv.writer(csvf)
    writer.writerow(["cfg_dir", "old_path", "new_path", "old_basename", "new_basename", "status"])
    for e in rename_entries:
        writer.writerow([e["cfg_dir"], e["old_path"], e["new_path"], e["old_basename"], e["new_basename"], "DRY-RUN" if DRY_RUN else "PENDING"])

print(f"Found {len(rename_entries)} images to rename under logos. Dry-run = {DRY_RUN}")
print(f"Per-file backups stored under: {BACKUP_ROOT}")
print(f"Pre-run report: {REPORT_CSV}")

if DRY_RUN:
    print("Dry-run only. Inspect the report and backups. Set DRY_RUN = False to apply changes.")
    # stop here in dry-run
    raise SystemExit("Dry-run complete.")

# ---------- APPLY CHANGES ----------
# 1) Rename image files
applied = []
for e in rename_entries:
    src = Path(e["old_path"])
    dst = Path(e["new_path"])
    try:
        src.rename(dst)
        e["status"] = "RENAMED"
        applied.append(e)
    except Exception as ex:
        e["status"] = f"ERROR_RENAME: {ex}"

# 2) Update config.xml in each cfg_dir:
# For each cfg_dir, load XML, find <record> elements and update 'from' attributes equal to old_stem -> new_stem
for cfg_dir in cfg_dirs:
    cfg_file = cfg_dir / "config.xml"
    if not cfg_file.exists():
        # skip if missing
        continue
    try:
        tree = ET.parse(cfg_file)
        root = tree.getroot()
        # build local mapping for this cfg_dir
        local_map = {}
        for e in rename_entries:
            if Path(e["cfg_dir"]) == cfg_dir:
                local_map[e["old_stem"]] = e["new_stem"]
        # find all elements named 'record' anywhere
        changed = False
        for rec in root.iter():
            if rec.tag.lower().endswith("record"):  # handle possible namespaces
                if "from" in rec.attrib:
                    v = rec.attrib["from"]
                    if v in local_map:
                        rec.set("from", local_map[v])
                        changed = True
        if changed:
            # write updated config.xml (overwrite original, but we already backed it up)
            tree.write(cfg_file, encoding="utf-8", xml_declaration=True)
    except Exception as ex:
        print(f"[WARN] Failed to update config.xml at {cfg_file}: {ex}")

# 3) Update other XML files under logos root:
# Replace attribute values exactly equal to old_stem with new_stem (for safety)
# Build global mapping old_stem -> new_stem (if same old_stem used in multiple dirs, mapping is local per-dir;
# we'll update only when attribute contains a path or we can detect directory context. To be safe we only update
# attributes equal to old_stem where the xml file lives in the same cfg_dir or descendant.)
global_map_by_dir = {}
for e in rename_entries:
    d = e["cfg_dir"]
    global_map_by_dir.setdefault(d, {})[e["old_stem"]] = e["new_stem"]

# For each xml file under LOGOS_ROOT, load and update attributes if the file is within a cfg_dir's tree
for xmlf in LOGOS_ROOT.rglob("*.xml"):
    try:
        xmlf_path = xmlf.resolve()
        tree = ET.parse(xmlf)
        root = tree.getroot()
        updated = False
        # find which cfg_dir context applies (the nearest ancestor cfg_dir)
        applicable_map = {}
        for cfg_dir in cfg_dirs:
            try:
                if xmlf_path.is_relative_to(cfg_dir.resolve()):
                    # use this cfg_dir mapping
                    applicable_map.update(global_map_by_dir.get(str(cfg_dir), {}))
            except Exception:
                # Path.is_relative_to is python3.9+; fallback check
                try:
                    xmlf_path.relative_to(cfg_dir.resolve())
                    applicable_map.update(global_map_by_dir.get(str(cfg_dir), {}))
                except Exception:
                    pass
        if not applicable_map:
            # no relevant mappings for this xml file
            continue
        # iterate elements and attributes
        for elem in root.iter():
            # check all attributes
            for k, v in list(elem.attrib.items()):
                if v in applicable_map:
                    elem.set(k, applicable_map[v])
                    updated = True
        if updated:
            tree.write(xmlf, encoding="utf-8", xml_declaration=True)
    except Exception as ex:
        print(f"[WARN] failed to process XML {xmlf}: {ex}")

# 4) Final report write (overwrite previous)
with open(REPORT_CSV, "w", newline="", encoding="utf-8") as csvf:
    writer = csv.writer(csvf)
    writer.writerow(["cfg_dir", "old_path", "new_path", "old_basename", "new_basename", "status"])
    for e in rename_entries:
        writer.writerow([e["cfg_dir"], e["old_path"], e["new_path"], e["old_basename"], e["new_basename"], e["status"]])

print("Done. Renamed images and updated config.xml files where applicable.")
print(f"Backups are in: {BACKUP_ROOT}")
print(f"Rename report: {REPORT_CSV}")
print("Clear FM graphics cache and reload graphics in-game.")
