#!/usr/bin/env python3
"""
Sync CHANGELOG.md from GitHub Releases.

This script fetches release notes from GitHub and generates a CHANGELOG.md file.
It preserves archived changelog entries (pre-automation) at the bottom.

Usage:
    python scripts/changelog_sync.py                  # Sync from GitHub Releases
    python scripts/changelog_sync.py --limit 20       # Fetch last 20 releases
    python scripts/changelog_sync.py --dry-run        # Preview without writing
"""

import argparse
import os
import re
import sys
from datetime import datetime
from pathlib import Path

try:
    import httpx
except ImportError:
    print("Error: httpx not installed. Run: pip install httpx", file=sys.stderr)
    sys.exit(1)


GITHUB_REPO = "DaveDev42/claude-worktree"
ARCHIVE_MARKER = "## Archived Changelog"
CHANGELOG_HEADER = """# Changelog

All notable changes are documented in [GitHub Releases](https://github.com/{repo}/releases).

This file is automatically generated from GitHub Release notes. To update, run:
```bash
python scripts/changelog_sync.py
```

For older releases (v0.1.0 - v0.9.2), see the [Archived Changelog](#archived-changelog) below.

---

"""


class ChangelogSyncError(Exception):
    """Base exception for changelog sync errors."""

    pass


def fetch_releases(limit: int = 20, token: str | None = None) -> list[dict]:
    """
    Fetch releases from GitHub API.

    Args:
        limit: Maximum number of releases to fetch
        token: GitHub API token (optional, uses GITHUB_TOKEN env var)

    Returns:
        List of release data dictionaries

    Raises:
        ChangelogSyncError: If API request fails
    """
    headers = {"Accept": "application/vnd.github+json"}
    if token or (token := os.getenv("GITHUB_TOKEN")):
        headers["Authorization"] = f"Bearer {token}"

    url = f"https://api.github.com/repos/{GITHUB_REPO}/releases"
    params = {"per_page": limit}

    try:
        with httpx.Client() as client:
            response = client.get(url, headers=headers, params=params, timeout=10.0)
            response.raise_for_status()
            return response.json()
    except httpx.HTTPStatusError as e:
        raise ChangelogSyncError(
            f"GitHub API error: {e.response.status_code} - {e.response.text}"
        ) from e
    except httpx.RequestError as e:
        raise ChangelogSyncError(f"Network error: {e}") from e


def parse_release_date(published_at: str) -> str:
    """
    Convert ISO 8601 timestamp to YYYY-MM-DD format.

    Args:
        published_at: ISO 8601 timestamp from GitHub

    Returns:
        Date in YYYY-MM-DD format

    Examples:
        >>> parse_release_date("2025-11-09T16:32:13Z")
        "2025-11-09"
    """
    if not published_at:
        return datetime.now().strftime("%Y-%m-%d")

    dt = datetime.fromisoformat(published_at.replace("Z", "+00:00"))
    return dt.strftime("%Y-%m-%d")


def convert_release_to_changelog(release: dict) -> str:
    """
    Convert GitHub Release to CHANGELOG.md format.

    Args:
        release: Release data from GitHub API

    Returns:
        Formatted changelog entry

    Example output:
        ## [0.10.27] - 2025-11-09

        ### Features
        - Feature description (#123)

        ### Bug Fixes
        - Fix description (#124)
    """
    tag = release.get("tag_name", "")
    version = tag.lstrip("v")
    date = parse_release_date(release.get("published_at", ""))
    body = (release.get("body") or "").strip()

    # Start with version header
    lines = [f"## [{version}] - {date}\n"]

    if body:
        # Clean up auto-generated content
        body = re.sub(r"Auto-released from merged PR #\d+\s*", "", body)
        body = re.sub(r"\*\*Full Changelog\*\*:.*", "", body)
        body = body.strip()

        if body:
            lines.append(body)
            lines.append("")

    return "\n".join(lines)


def read_archived_changelog() -> str:
    """
    Read archived changelog entries from existing CHANGELOG.md.

    Returns:
        Archived changelog content, or empty string if not found
    """
    changelog_path = Path("CHANGELOG.md")
    if not changelog_path.exists():
        return ""

    content = changelog_path.read_text()

    # Find archived section
    if ARCHIVE_MARKER in content:
        parts = content.split(ARCHIVE_MARKER, 1)
        return ARCHIVE_MARKER + parts[1]

    # If no archive marker, check if this is old changelog (has versions <= 0.9.2)
    if "## [0.9.2]" in content or "## [0.1.0]" in content:
        # Extract all content after main header
        match = re.search(r"^#[^\n]+\n+(.*)", content, re.DOTALL)
        if match:
            old_content = match.group(1).strip()
            return f"{ARCHIVE_MARKER} (v0.1.0 - v0.9.2)\n\n{old_content}"

    return ""


def generate_changelog(releases: list[dict], archived: str = "") -> str:
    """
    Generate complete CHANGELOG.md content.

    Args:
        releases: List of release data from GitHub
        archived: Archived changelog content

    Returns:
        Complete CHANGELOG.md content
    """
    parts = [CHANGELOG_HEADER.format(repo=GITHUB_REPO)]

    # Add releases
    for release in releases:
        if not release.get("draft", False):  # Skip draft releases
            parts.append(convert_release_to_changelog(release))

    # Add comparison links
    parts.append("\n---\n")
    parts.append("## Version Comparison Links\n")

    for i, release in enumerate(releases):
        if release.get("draft", False):
            continue

        tag = release["tag_name"]
        version = tag.lstrip("v")

        if i == 0:
            # Latest version - compare to HEAD
            parts.append(f"[Unreleased]: https://github.com/{GITHUB_REPO}/compare/{tag}...HEAD")

        if i < len(releases) - 1:
            # Compare to previous version
            next_tag = releases[i + 1]["tag_name"]
            parts.append(
                f"[{version}]: https://github.com/{GITHUB_REPO}/compare/{next_tag}...{tag}"
            )
        else:
            # First release - link to tag
            parts.append(f"[{version}]: https://github.com/{GITHUB_REPO}/releases/tag/{tag}")

    # Add archived changelog
    if archived:
        parts.append("\n---\n")
        parts.append(archived)

    return "\n".join(parts) + "\n"


def main() -> None:
    """Main changelog sync workflow."""
    parser = argparse.ArgumentParser(
        description="Sync CHANGELOG.md from GitHub Releases",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python scripts/changelog_sync.py                  # Sync from GitHub Releases
  python scripts/changelog_sync.py --limit 20       # Fetch last 20 releases
  python scripts/changelog_sync.py --dry-run        # Preview without writing
        """,
    )

    parser.add_argument(
        "--limit",
        type=int,
        default=20,
        help="Maximum number of releases to fetch (default: 20)",
    )
    parser.add_argument(
        "--token",
        help="GitHub API token (default: GITHUB_TOKEN env var)",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Preview without writing to CHANGELOG.md",
    )

    args = parser.parse_args()

    try:
        print("🔄 Syncing CHANGELOG.md from GitHub Releases...\n")

        # Step 1: Fetch releases
        print(f"Step 1: Fetching last {args.limit} releases...")
        releases = fetch_releases(limit=args.limit, token=args.token)
        print(f"✅ Fetched {len(releases)} releases")

        # Step 2: Read archived changelog
        print("\nStep 2: Reading archived changelog...")
        archived = read_archived_changelog()
        if archived:
            print("✅ Found archived changelog entries")
        else:
            print("ℹ️  No archived changelog found")

        # Step 3: Generate new changelog
        print("\nStep 3: Generating CHANGELOG.md...")
        changelog = generate_changelog(releases, archived)

        # Step 4: Write or preview
        if args.dry_run:
            print("\n[DRY-RUN] Generated CHANGELOG.md:\n")
            print("=" * 80)
            print(changelog)
            print("=" * 80)
            print("\n⚠️  DRY-RUN mode: No changes written")
        else:
            changelog_path = Path("CHANGELOG.md")
            changelog_path.write_text(changelog)
            print(f"✅ Written to {changelog_path}")

        print("\n✅ Changelog sync completed successfully!")

    except ChangelogSyncError as e:
        print(f"\n❌ Sync failed: {e}", file=sys.stderr)
        sys.exit(1)
    except KeyboardInterrupt:
        print("\n\n⚠️  Interrupted by user", file=sys.stderr)
        sys.exit(130)
    except Exception as e:
        print(f"\n❌ Unexpected error: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
