#!/usr/bin/env python3
"""
Publish a new version to winget-pkgs.

Usage: ./scripts/winget-publish.py <version>
Example: ./scripts/winget-publish.py 0.1.99

Requirements: gh, git, curl (or Python requests)
"""

import hashlib
import os
import re
import shutil
import subprocess
import sys
import tempfile
import urllib.request
from pathlib import Path


PACKAGE_ID = "sinelaw.fresh-editor"
MANIFEST_BASE = "manifests/s/sinelaw/fresh-editor"
WINGET_REPO = "microsoft/winget-pkgs"
WINDOWS_INSTALLERS = [
    ("x64", "x86_64-pc-windows-msvc"),
    ("arm64", "aarch64-pc-windows-msvc"),
]


def run(cmd: list[str], check: bool = True, capture: bool = False) -> subprocess.CompletedProcess:
    """Run a command and optionally capture output."""
    print(f"  $ {' '.join(cmd)}")
    return subprocess.run(cmd, check=check, capture_output=capture, text=True)


def get_sha256(url: str) -> str:
    """Download file and compute SHA256."""
    print(f"Downloading {url}...")
    with tempfile.NamedTemporaryFile(delete=False) as tmp:
        with urllib.request.urlopen(url) as response:
            shutil.copyfileobj(response, tmp)
        tmp_path = tmp.name

    sha256 = hashlib.sha256()
    with open(tmp_path, "rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            sha256.update(chunk)

    Path(tmp_path).unlink()
    return sha256.hexdigest()


def release_asset_url(version: str, target: str) -> str:
    return f"https://github.com/sinelaw/fresh/releases/download/v{version}/fresh-editor-{target}.zip"


def render_installers(version: str, hashes: dict[str, str]) -> str:
    lines = ["Installers:"]
    for arch, target in WINDOWS_INSTALLERS:
        lines.extend(
            [
                f"  - Architecture: {arch}",
                f"    InstallerUrl: {release_asset_url(version, target)}",
                f"    InstallerSha256: {hashes[arch]}",
            ]
        )
    return "\n".join(lines)


def main():
    if len(sys.argv) != 2:
        print(f"Usage: {sys.argv[0]} <version>")
        print(f"Example: {sys.argv[0]} 0.1.99")
        sys.exit(1)

    version = sys.argv[1]
    branch_name = f"{PACKAGE_ID}-{version}"

    print(f"Publishing {PACKAGE_ID} version {version}")
    print("Installer URLs:")
    for arch, target in WINDOWS_INSTALLERS:
        print(f"  {arch}: {release_asset_url(version, target)}")
    print()

    # Check gh is authenticated
    result = run(["gh", "auth", "status"], check=False, capture=True)
    if result.returncode != 0:
        print("Please authenticate with GitHub first: gh auth login")
        sys.exit(1)

    # Compute SHA256
    print("Computing SHA256...")
    hashes = {}
    for arch, target in WINDOWS_INSTALLERS:
        hashes[arch] = get_sha256(release_asset_url(version, target))
        print(f"SHA256 ({arch}): {hashes[arch]}")
    print()

    # Use cache directory for persistent clone
    cache_dir = Path.home() / ".cache" / "winget-publish"
    cache_dir.mkdir(parents=True, exist_ok=True)
    repo_path = cache_dir / "winget-pkgs"

    if repo_path.exists():
        # Verify remote and fetch
        print("Using cached winget-pkgs clone...")
        os.chdir(repo_path)

        result = run(["git", "remote", "get-url", "origin"], capture=True)
        origin_url = result.stdout.strip()
        if "sinelaw/winget-pkgs" not in origin_url:
            print(f"Error: Unexpected origin remote: {origin_url}")
            print(f"Expected sinelaw/winget-pkgs. Delete {repo_path} and retry.")
            sys.exit(1)

        # Clean up any leftover state and sync
        run(["git", "checkout", "master"], check=False)
        run(["git", "reset", "--hard", "HEAD"])
        run(["git", "clean", "-fd"])
    else:
        # Fork and clone
        print("Forking/cloning winget-pkgs...")
        run(["gh", "repo", "fork", WINGET_REPO, "--clone=false"], check=False)  # Ensure fork exists
        run(["gh", "repo", "clone", "sinelaw/winget-pkgs", str(repo_path)])
        os.chdir(repo_path)

    # Add upstream remote and fetch latest
    print("Fetching latest from upstream...")
    run(["git", "remote", "add", "upstream", f"https://github.com/{WINGET_REPO}.git"], check=False)
    run(["git", "fetch", "upstream", "master"])
    run(["git", "reset", "--hard", "upstream/master"])

    # Sync fork with upstream (for the PR later)
    print("Syncing fork with upstream...")
    run(["gh", "repo", "sync", "--force"])

    # Find latest version
    print("Finding latest existing version...")
    manifest_path = repo_path / MANIFEST_BASE
    if not manifest_path.exists():
        print(f"Error: Package not found in upstream: {MANIFEST_BASE}")
        print("The package must exist in microsoft/winget-pkgs before using this script.")
        print("Please submit the initial version manually first.")
        sys.exit(1)

    versions = sorted(manifest_path.iterdir(), key=lambda p: list(map(int, p.name.split("."))))
    if not versions:
        print("Error: No existing versions found")
        sys.exit(1)

    latest_version = versions[-1].name
    print(f"Latest version: {latest_version}")

    # Create branch (delete if leftover from a previous attempt)
    print(f"Creating branch {branch_name}...")
    run(["git", "branch", "-D", branch_name], check=False)
    run(["git", "checkout", "-b", branch_name])

    # Copy manifests
    old_path = manifest_path / latest_version
    new_path = manifest_path / version
    print(f"Copying manifests from {latest_version} to {version}...")
    shutil.copytree(old_path, new_path)

    # Update manifests
    print("Updating version, URLs, and SHA256 hashes...")
    for yaml_file in new_path.glob("*.yaml"):
        content = yaml_file.read_text()

        # Update PackageVersion
        content = re.sub(r"^PackageVersion:.*$", f"PackageVersion: {version}", content, flags=re.MULTILINE)

        # Update installer entries only in the manifest file that owns them.
        if "Installers:" in content:
            # Keep this in sync with the existing multi-file manifest style in
            # winget-pkgs: installer metadata at the root, arch-specific
            # download details under Installers.
            if "InstallerType:" not in content:
                content = re.sub(
                    r"^(PackageVersion:.*)$",
                    r"\1\nInstallerLocale: en-US\nInstallerType: zip\nScope: user\nNestedInstallerType: portable\nUpgradeBehavior: uninstallPrevious\nNestedInstallerFiles:\n  - RelativeFilePath: fresh.exe\n    PortableCommandAlias: fresh",
                    content,
                    flags=re.MULTILINE,
                )
            content = re.sub(
                r"(?ms)^Installers:\n.*?(?=^ManifestType:)",
                render_installers(version, hashes) + "\n",
                content,
            )

        # Update ReleaseNotesUrl
        content = re.sub(
            r"^ReleaseNotesUrl:.*$",
            f"ReleaseNotesUrl: https://github.com/sinelaw/fresh/releases/tag/v{version}",
            content,
            flags=re.MULTILINE,
        )

        # Ensure UpgradeBehavior is set (needed for winget upgrade to replace instead of install alongside)
        if "UpgradeBehavior" not in content:
            content = re.sub(
                r"^(\s*)(NestedInstallerType: portable)$",
                r"\1\2\n\1UpgradeBehavior: uninstallPrevious",
                content,
                flags=re.MULTILINE,
            )

        yaml_file.write_text(content)

    # Show updated manifests
    print()
    print("Updated manifests:")
    for yaml_file in sorted(new_path.glob("*.yaml")):
        print(f"--- {yaml_file.name} ---")
        print(yaml_file.read_text())

    # Commit and push
    print("Committing and pushing...")
    run(["git", "add", "."])
    run(["git", "commit", "-m", f"New version: {PACKAGE_ID} version {version}"])
    run(["git", "push", "-u", "origin", branch_name])

    # Create PR
    print("Creating pull request...")
    pr_body = """## Description
New version of fresh-editor.

## Checklist
- [x] Have you signed the [Contributor License Agreement](https://cla.opensource.microsoft.com/microsoft/winget-pkgs)?
- [x] Have you checked that there aren't other open [pull requests](https://github.com/microsoft/winget-pkgs/pulls) for the same manifest update/change?
- [x] This PR only modifies one (1) manifest
- [x] Have you validated your manifest locally with `winget validate --manifest <path>`?
- [x] Have you tested your manifest locally with `winget install --manifest <path>`?
"""

    result = run(
        [
            "gh", "pr", "create",
            "--repo", WINGET_REPO,
            "--title", f"New version: {PACKAGE_ID} version {version}",
            "--body", pr_body,
            "--head", f"sinelaw:{branch_name}",
        ],
        capture=True,
    )

    pr_url = result.stdout.strip()
    print()
    print(f"Pull request created: {pr_url}")


if __name__ == "__main__":
    main()
