#!/usr/bin/env python3
"""Create reviewable Gentoo source ebuilds for real KLookup releases.

Run from the root of arrans_overlay. GitHub Actions creates the PR from the
resulting working-tree diff; this script never pushes or creates a PR itself.
"""

import json
import os
from pathlib import Path
import re
import subprocess
import urllib.request

PACKAGE_DIR = Path('app-misc/klookup')
LIVE_EBUILD = PACKAGE_DIR / 'klookup-9999.ebuild'
RELEASES_URL = 'https://api.github.com/repos/arran4/klookup/releases?per_page=100'
STABLE_TAG = re.compile(r'^v?(?P<version>[0-9]+\.[0-9]+\.[0-9]+)$')
RELEASE_EBUILD = re.compile(r'^klookup-(?P<version>\d+\.\d+\.\d+)(?:-r[1-9]\d*)?\.ebuild$')


def get_releases():
    headers = {'Accept': 'application/vnd.github+json',
               'User-Agent': 'arrans-overlay-klookup-source-updater'}
    if os.environ.get('GITHUB_TOKEN'):
        headers['Authorization'] = f"Bearer {os.environ['GITHUB_TOKEN']}"
    req = urllib.request.Request(RELEASES_URL, headers=headers)
    with urllib.request.urlopen(req, timeout=30) as response:
        releases = json.load(response)
    if not isinstance(releases, list):
        raise ValueError('GitHub releases API did not return an array')
    return releases


def eligible_releases(releases):
    seen = set()
    for release in releases:
        if not isinstance(release, dict) or release.get('draft') or release.get('prerelease'):
            continue
        tag = release.get('tag_name')
        match = STABLE_TAG.fullmatch(tag) if isinstance(tag, str) else None
        if match is None:
            continue
        version = match.group('version')
        if version in seen:
            continue
        seen.add(version)
        yield tag, version


def release_ebuild(tag, version):
    """Reuse the live ebuild's tested dependency list rather than drifting."""
    source = LIVE_EBUILD.read_text(encoding='utf-8')
    # Acorn is MIT licensed: do not generate incorrect license metadata.
    license_match = re.search(r'^LICENSE="([^"]+)"$', source, re.MULTILINE)
    if not license_match or 'MIT' not in license_match.group(1).split():
        raise ValueError('Live KLookup ebuild must declare MIT for bundled Acorn')
    if 'inherit cmake git-r3' not in source:
        raise ValueError('Live ebuild layout changed: review source template')
    if 'EGIT_REPO_URI="https://github.com/arran4/klookup.git"' not in source:
        raise ValueError('Live ebuild Git URL changed: review release template')
    source = source.replace('inherit cmake git-r3', 'inherit cmake', 1)
    source = source.replace(
        'EGIT_REPO_URI="https://github.com/arran4/klookup.git"',
        'SRC_URI="\n\thttps://github.com/arran4/klookup/archive/refs/tags/'
        f'{tag}.tar.gz\n\t\t-> ${{P}}.tar.gz\n"\nS="${{WORKDIR}}/klookup-${{PV}}"',
        1,
    )
    source = source.replace('KEYWORDS=""', 'KEYWORDS="~amd64"', 1)
    return source.replace(
        '# Distributed under the terms of the GNU General Public License v2\n\n',
        '# Distributed under the terms of the GNU General Public License v2\n\n'
        '# Generated by scripts/update_klookup_source.py\n', 1)


def create_release(tag, version):
    # A -rN revision packages the same upstream release; never regenerate an
    # unpatched -r0 if a downstream bugfix revision already exists.
    if any((match := RELEASE_EBUILD.fullmatch(ebuild.name)) and
           match.group('version') == version
           for ebuild in PACKAGE_DIR.glob('klookup-*.ebuild')):
        return False
    dest = PACKAGE_DIR / f'klookup-{version}.ebuild'
    content = release_ebuild(tag, version)
    dest.write_text(content, encoding='utf-8')
    url = f'https://github.com/arran4/klookup/archive/refs/tags/{tag}.tar.gz'
    subprocess.run(
        ['g2', 'manifest', 'upsert-from-url', url,
         f'klookup-{version}.tar.gz', str(PACKAGE_DIR / 'Manifest')],
        check=True,
    )
    return True


def prune_old_stable_releases(keep_version):
    """Keep only the newest stable version, including any -rN revisions."""
    removed_versions = []
    for ebuild in PACKAGE_DIR.glob('klookup-*.ebuild'):
        match = RELEASE_EBUILD.fullmatch(ebuild.name)
        if match and match.group('version') != keep_version:
            removed_versions.append(match.group('version'))
            ebuild.unlink()
            (Path('metadata/md5-cache/app-misc') / ebuild.stem).unlink(missing_ok=True)
    manifest = PACKAGE_DIR / 'Manifest'
    if removed_versions and manifest.exists():
        obsolete_distfiles = {f'klookup-{version}.tar.gz' for version in removed_versions}
        original = manifest.read_text(encoding='utf-8')
        kept = [line for line in original.splitlines(keepends=True)
                if not (line.startswith('DIST ') and
                        line.split()[1] in obsolete_distfiles)]
        manifest.write_text(''.join(kept), encoding='utf-8')
    return bool(removed_versions)


def main():
    if not LIVE_EBUILD.is_file():
        raise FileNotFoundError(f'Run from overlay root: missing {LIVE_EBUILD}')
    releases = list(eligible_releases(get_releases()))
    if not releases:
        print('No published stable KLookup releases')
        return
    # GitHub's API order is not a reliable semantic-version comparison.
    tag, version = max(releases,
                       key=lambda item: tuple(int(part) for part in item[1].split('.')))
    updated = create_release(tag, version)
    updated = prune_old_stable_releases(version) or updated
    if updated:
        # This lightweight Ubuntu job does not have the Gentoo/GURU/hyproverlay
        # masters required to generate an authoritative Portage metadata cache.
        # Defer cache reconciliation to the separate full-master workflow (#862);
        # a missing cache must not prevent a reviewable source ebuild/Manifest PR.
        print('Updated KLookup ebuild and Manifest; metadata cache deferred')
    else:
        print('Latest stable KLookup release already packaged')


if __name__ == '__main__':
    main()
