#!/usr/bin/env python3
"""
Gold AutoTrade — Client Updater

Checks for updates against the license server and applies them.

Usage:
  python3 -m src.updater --check          # Check for updates (dry-run)
  python3 -m src.updater --update         # Download and apply update
  python3 -m src.updater --status         # Show current version info
"""

from __future__ import annotations

import json
import os
import shutil
import sys
import tempfile
import urllib.request
import zipfile
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
VERSION_FILE = ROOT / "VERSION"
MANIFEST_URL = "https://license.huipin199.com/api/update/manifest"


def current_version() -> str:
    if VERSION_FILE.exists():
        return VERSION_FILE.read_text().strip()
    return "0.0.0"


def fetch_manifest() -> dict | None:
    try:
        req = urllib.request.Request(MANIFEST_URL)
        with urllib.request.urlopen(req, timeout=15) as resp:
            return json.loads(resp.read().decode())
    except Exception as e:
        print(f"  ✗ Cannot reach update server: {e}")
        return None


def check(manifest: dict | None = None) -> dict:
    """Return {'update_available': bool, 'current': str, 'latest': str, ...}"""
    cur = current_version()
    if manifest is None:
        manifest = fetch_manifest()
    if manifest is None:
        return {"update_available": False, "current": cur, "error": "Cannot reach server"}

    latest = manifest.get("version", "0.0.0")
    trial_pkg = manifest.get("packages", {}).get("trial", {})
    changelog = manifest.get("changelog", trial_pkg.get("changelog", ""))
    release_date = manifest.get("release_date", trial_pkg.get("release_date", ""))

    update_available = _version_newer(latest, cur)

    return {
        "update_available": update_available,
        "current": cur,
        "latest": latest,
        "changelog": changelog,
        "release_date": release_date,
        "download_url": trial_pkg.get("url", ""),
        "file_size": trial_pkg.get("size", 0),
        "sha256": trial_pkg.get("sha256", ""),
    }


def apply_update(manifest: dict | None = None) -> bool:
    """Download and apply the update. Returns True on success."""
    info = check(manifest)
    if not info["update_available"]:
        print(f"Already up to date (v{info['current']}).")
        return True

    download_url = info["download_url"]
    if not download_url:
        print("ERROR: No download URL in manifest.")
        return False

    # Use the public download URL
    download_full = f"https://huipin199.com{download_url}"
    print(f"Downloading v{info['latest']}...")
    print(f"  From: {download_full}")
    print(f"  Size: {info['file_size'] / 1024 / 1024:.1f}MB")
    print(f"  Changelog: {info['changelog']}")
    print()

    # Download to temp file
    tmpdir = Path(tempfile.mkdtemp(prefix="gat_update_"))
    zip_path = tmpdir / f"update-v{info['latest']}.zip"
    try:
        urllib.request.urlretrieve(download_full, str(zip_path))
    except Exception as e:
        print(f"ERROR: Download failed: {e}")
        shutil.rmtree(tmpdir, ignore_errors=True)
        return False

    # Verify SHA256
    if info["sha256"]:
        print("  Verifying...")
        actual = _sha256(zip_path)
        if actual != info["sha256"]:
            print(f"  ✗ SHA256 mismatch!")
            print(f"    Expected: {info['sha256'][:16]}...")
            print(f"    Got:      {actual[:16]}...")
            shutil.rmtree(tmpdir, ignore_errors=True)
            return False
        print(f"  ✓ SHA256 verified")

    # Extract to temp
    extract_dir = tmpdir / "extracted"
    extract_dir.mkdir()
    print(f"  Extracting...")
    with zipfile.ZipFile(zip_path, "r") as zf:
        # The zip contains a single top-level directory (GoldAutoTrade-Trial-vX.X.X-trial)
        zf.extractall(str(extract_dir))

    # Find the extracted root dir
    extracted_items = list(extract_dir.iterdir())
    if len(extracted_items) != 1 or not extracted_items[0].is_dir():
        print("ERROR: Unexpected zip structure.")
        shutil.rmtree(tmpdir, ignore_errors=True)
        return False
    new_root = extracted_items[0]

    # Create backup
    backup_dir = ROOT.parent / f".backup_v{info['current']}"
    if not backup_dir.exists():
        print(f"  Backing up to {backup_dir.name}...")
        shutil.copytree(ROOT, backup_dir, symlinks=True,
                        ignore=lambda d, f: [x for x in f if x in ('__pycache__', '.venv', 'node_modules', 'logs', 'data', 'dist')])

    # Apply update: copy new files over old
    print(f"  Applying update...")
    _copy_over(new_root, ROOT)

    # Update VERSION file
    VERSION_FILE.write_text(info["latest"])

    # Cleanup
    shutil.rmtree(tmpdir, ignore_errors=True)

    print(f"  ✓ Updated to v{info['latest']}")
    print()
    print(f"  Backup saved at: {backup_dir}")
    print(f"  To rollback: mv {backup_dir.name} {ROOT.name}")
    return True


def _copy_over(src: Path, dst: Path) -> None:
    """Copy src contents over dst, overwriting existing files."""
    for item in src.iterdir():
        target = dst / item.name
        if item.is_dir():
            if target.exists() and not target.is_dir():
                target.unlink()
            target.mkdir(exist_ok=True)
            _copy_over(item, target)
        else:
            shutil.copy2(item, target)


def _version_newer(a: str, b: str) -> bool:
    """Compare version strings like '1.0.1-trial'."""
    def _parse(v: str) -> list:
        parts = v.split("-")[0].split(".")
        return [int(p) for p in parts if p.isdigit()]

    return _parse(a) > _parse(b)


def _sha256(path: Path) -> str:
    import hashlib
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            h.update(chunk)
    return h.hexdigest()


# ── CLI ──────────────────────────────────────────────────────────────

def main() -> None:
    import argparse
    parser = argparse.ArgumentParser(description="Gold AutoTrade Updater")
    group = parser.add_mutually_exclusive_group()
    group.add_argument("--check", action="store_true", help="Check for updates (dry-run)")
    group.add_argument("--update", action="store_true", help="Download and apply update")
    group.add_argument("--status", action="store_true", help="Show current version info")
    args = parser.parse_args()

    if args.update:
        success = apply_update()
        sys.exit(0 if success else 1)
    elif args.status:
        cur = current_version()
        print(f"Current version: v{cur}")
        print(f"Install path:    {ROOT}")
        print(f"Update server:   {MANIFEST_URL}")
    else:
        # Default: check
        info = check()
        print(f"Current version: v{info['current']}")
        if info.get("error"):
            print(f"Error: {info['error']}")
            sys.exit(1)
        print(f"Latest version:  v{info['latest']}")
        if info["update_available"]:
            print(f"Update available!")
            print(f"  Changelog: {info['changelog']}")
            print(f"  Date:      {info['release_date']}")
            print(f"  Size:      {info['file_size'] / 1024 / 1024:.1f}MB")
            print()
            print(f"To update: python3 -m src.updater --update")
        else:
            print(f"Status: up to date")


if __name__ == "__main__":
    main()
