#!/usr/bin/env python3
"""
profile-exporter/export_profile.py
Reads FIFA 23 local profile/settings files and exports what can be read.

FIFA 23 uses the Frostbite FBCHUNKS container format for saves.
This exporter handles:
  - fifasetup.ini        → plain text, fully readable
  - ProfileOptions       → FBCHUNKS binary (partial parse of header + string scan)
  - Settings*            → FBCHUNKS binary (contains "Personal Settings 1" label)
  - filesystemcache/     → JSON files, fully readable
  - anadius.cfg          → Valve KeyValues format, contains persona info

Usage:
  python3 export_profile.py [--output out.json]
"""

import struct
import json
import re
import sys
from pathlib import Path
from datetime import datetime

STEAM_PFX = Path.home() / ".local/share/Steam/steamapps/compatdata/3887260930/pfx"
UMU_PFX   = Path.home() / "Games/umu/fifa23-tools/pfx"
GAME_DIR  = Path("/mnt/games/FIFA 23")

DOCS_PATHS = [
    STEAM_PFX / "drive_c/users/steamuser/Documents/FIFA 23",
    UMU_PFX   / "drive_c/users/steamuser/Documents/FIFA 23",
]

def find_docs() -> Path | None:
    for p in DOCS_PATHS:
        if p.exists():
            return p
    return None


def read_ini(path: Path) -> dict:
    out = {}
    for line in path.read_text(errors="replace").splitlines():
        line = line.strip()
        if "=" in line and not line.startswith(";") and not line.startswith("#"):
            k, _, v = line.partition("=")
            out[k.strip()] = v.strip().strip('"')
    return out


def parse_fbchunks_header(data: bytes) -> dict:
    """Extract what we can from the FBCHUNKS container without a full format spec."""
    if not data[:8] == b"FBCHUNKS":
        return {"error": "not FBCHUNKS"}
    info: dict = {"magic": "FBCHUNKS", "size_bytes": len(data)}
    # The label string appears at offset 0x12 in Settings files
    # Try to pull the first null-terminated ASCII string after offset 8
    try:
        label_start = 0x12
        end = data.index(b"\x00", label_start)
        label = data[label_start:end].decode("ascii", errors="ignore")
        if label:
            info["label"] = label
    except (ValueError, IndexError):
        pass
    # String-scan for recognisable names/values
    strings = re.findall(rb"[ -~]{6,}", data)
    info["readable_strings_sample"] = [s.decode("ascii") for s in strings[:40]]
    return info


def read_filesystem_cache(docs: Path) -> dict:
    cache_dir = docs / "filesystemcache"
    out = {}
    if not cache_dir.exists():
        return out
    for f in cache_dir.rglob("*"):
        if f.is_file():
            try:
                out[str(f.relative_to(cache_dir))] = json.loads(f.read_text())
            except Exception:
                out[str(f.relative_to(cache_dir))] = f.read_text(errors="replace")
    return out


def parse_anadius_cfg(path: Path) -> dict:
    """Parse Valve KeyValues (VDF) format anadius.cfg for persona/user info."""
    out: dict = {}
    current_section: str | None = None
    for line in path.read_text(errors="replace").splitlines():
        line = line.strip()
        if line.startswith('"') and line.endswith('"') and "{" not in line:
            # lone quoted string — section name coming next
            current_section = line.strip('"')
        elif line.startswith('"') and "\t" in line:
            parts = [p.strip().strip('"') for p in line.split("\t") if p.strip()]
            if len(parts) == 2:
                key, val = parts[0], parts[1]
                section_key = f"{current_section}.{key}" if current_section else key
                out[section_key] = val
    return out


def export_settings_files(docs: Path) -> list:
    settings_dir = docs / "settings"
    entries = []
    if not settings_dir.exists():
        return entries
    for f in sorted(settings_dir.iterdir()):
        if f.is_file():
            data = f.read_bytes()
            entry: dict = {
                "file": f.name,
                "size_bytes": len(data),
                "mtime": datetime.fromtimestamp(f.stat().st_mtime).isoformat(),
            }
            if data[:8] == b"FBCHUNKS":
                entry["format"] = "FBCHUNKS"
                entry["header"] = parse_fbchunks_header(data)
            elif data and all(0x20 <= b < 0x7F or b in (0x09, 0x0A, 0x0D) for b in data[:256]):
                entry["format"] = "text"
                entry["content"] = f.read_text(errors="replace")[:2000]
            else:
                entry["format"] = "binary"
            entries.append(entry)
    return entries


def main():
    out_path = None
    if "--output" in sys.argv:
        idx = sys.argv.index("--output")
        out_path = Path(sys.argv[idx + 1])

    docs = find_docs()
    result: dict = {
        "exported_at": datetime.utcnow().isoformat() + "Z",
        "docs_path": str(docs) if docs else None,
    }

    # fifasetup.ini
    if docs:
        ini_path = docs / "fifasetup.ini"
        if ini_path.exists():
            result["fifasetup_ini"] = read_ini(ini_path)

    # anadius.cfg (persona / user info)
    anadius_cfg = GAME_DIR / "anadius.cfg"
    if anadius_cfg.exists():
        result["anadius_cfg"] = parse_anadius_cfg(anadius_cfg)

    # Settings/ save files
    if docs:
        result["settings_files"] = export_settings_files(docs)

    # filesystemcache/
    if docs:
        result["filesystem_cache"] = read_filesystem_cache(docs)

    output = json.dumps(result, indent=2, ensure_ascii=False)

    if out_path:
        out_path.write_text(output)
        print(f"Written to {out_path}")
    else:
        print(output)


if __name__ == "__main__":
    main()
