#!/usr/bin/python
# Copyright (C) 2024-present ScyllaDB
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
#
# Fetch from ScyllaDB's S3 bucket (downloads.scylladb.com) a pre-compiled
# version of Scylla for a desired release, ready to be run by tests (run.py).
# What we need to fetch is a "relocatable" package containing just the Scylla
# executable and the shared libraries it needs - we don't need a full OS and
# not even stuff like JMX or Python that cqlpy tests don't need.
#
# Can fetch released versions with names like:
#   * 5.4.7
#   * 5.4 (latest in the 5.4 branch, e.g., 5.4.7)
#   * 5.4.0~rc2 (prerelease)
#   * 2021.1.9 (Enterprise release)
#   * 2023.1 (latest in this branch, Enterprise release)

import boto3
import botocore
import sys
import os
import subprocess

# Parse version 5.6.2 into the array [5, 6, 2]. Ignore rc numbers.
def parse_version(version):
    return version.split('~')[0].split('.')

def s3_download(s3, bucket, object_key, out):
    print(f'Downloading {object_key}... ', end='')
    if not sys.stdout.isatty():
        sys.stdout.flush() # show the "Downloading" message at least
        bucket.download_file(object_key, out)
    else:
        # When interactive, show download progress:
        print('')
        objlen = s3.Object(bucket.name, object_key).content_length
        downloaded = 0
        def progress(chunk):
            nonlocal downloaded
            downloaded += chunk
            print('\33[2K\r%02d%%  %d MB' % (int(downloaded*100/objlen), int(downloaded/1024/1024)), end='')
        bucket.download_file(object_key, out, Callback=progress)
        print('')
    print('done.')

# Download Scylla release "release" (e.g., 5.4, 5.4.7, 2023.1, 2023.1.7)
# into a subdirectory of directory "dir" named by the specific release
# downloaded (e.g., 5.4.7). Returns the ready-to-use scylla executable
# (actually, a shell-script wrapper) in that subdirectory - or raise exception
# if this release cannot be found.
# If the same download was already done in the past, discover this as quickly
# as possible and don't download again. A 3-component release like 5.4.7
# doesn't even require a network lookup, a 2-component release like 5.4 does
# require a lookup to see if a newer 5.4 release appeared - but may not
# require a full download if the same release remains the latest.
def download_scylla(release, dir):
    scylla_arch = 'x86_64'

    v = parse_version(release)
    # If release has 3 components (e.g., 5.4.7) we know exactly which version
    # we are looking for, and can check if we already downloaded it without
    # even fetching the list of versions from s3:
    if len(v) == 3 and os.path.exists(dir + '/' + release + '/scylla_wrapper'):
        print(f'Already downloaded, in {dir}/{release}')
        return dir + '/' + release + '/scylla_wrapper'
    major = f'{v[0]}.{v[1]}'
    bucket = f'downloads.scylladb.com'
    scylla_arch_string = '.' + scylla_arch + '.'
    if int(v[0]) >= 2025: # New single release stream (no separate Enterprise)
        prefix = f'downloads/scylla/relocatable/scylladb-{major}/scylla-{release}'
    elif int(v[0]) >= 2023: # Enterprise release (new organization)
        prefix = f'downloads/scylla-enterprise/relocatable/scylladb-{major}/scylla-enterprise-{release}'
    elif int(v[0]) > 2000: # Enterprise release (old organization)
        prefix = f'downloads/scylla-enterprise/relocatable/scylladb-{major}/scylla-enterprise-{scylla_arch}-package-{release}'
        scylla_arch_string = '' # arch already restricted by prefix
    elif [int(v[0]),int(v[1])] <= [4,5]: # Open source (very old organization)
        prefix = f'downloads/scylla/relocatable/scylladb-{major}/scylla-package-{release}'
        scylla_arch_string = '' # no arch (only x86)
    elif [int(v[0]),int(v[1])] <= [5,1]: # Open source (old organization)
        prefix = f'downloads/scylla/relocatable/scylladb-{major}/scylla-{scylla_arch}-package-{release}'
        scylla_arch_string = '' # arch already restricted by prefix
    else: # Open source (new organization)
        prefix = f'downloads/scylla/relocatable/scylladb-{major}/scylla-{release}'
    # This prefix has many different packages belonging to all releases in
    # the same major version. We need to look only for those matching the
    # minor version, and take the highest minor number.

    # We set region_name and use unsigned (anonymous) requests to avoid
    # the need of the user to have to set up a valid $HOME/.aws/config
    # or $HOME/.aws/credentails.
    s3 = boto3.resource('s3', region_name='us-east-1', config=botocore.client.Config(signature_version=botocore.UNSIGNED))
    bucket = s3.Bucket(bucket)
    matches = bucket.objects.filter(Prefix=prefix)
    candidates = [o.key.removeprefix(prefix) for o in matches if scylla_arch_string in o.key]
    # candidates for release 5.4 look like ".2-0.20240117.6c625e8cd3c6.x86_64.tar.gz"
    # refering to 5.4.2. Remove the first character (can be . or -) and take
    # the one with highest number (assume it is up to two digits).
    # Some old Enterprise releases used the convension of rc7 instead of
    # 0~rc7, so the rcs come first in this sorting instead of last, so we
    # need to fix that too.
    def comparison_key(x):
        x = x[1:].replace('.', '-').split('-')[0]
        if x.startswith('rc'):
            # 0~rc1 came before 0, so let's add /, which is before 0
            return '/0~'+x
        elif x.startswith('0~rc'):
            return '/'+x
        else:
            return '%02d' % (int(x))
    candidates = sorted(candidates, key=comparison_key, reverse=True)
    if not candidates:
        raise Exception(f"Can't find release {release}")
    chosen = prefix + candidates[0]
    # different versions used different delimeters
    candidates[0] = candidates[0].replace('.', '-')
    minor = candidates[0].split('-')[1]
    chosen_release = release if len(v)==3 else major + '.' + minor
    print(f'Chosen download for ScyllaDB {release}: {chosen_release} ({chosen})')
    
    out_dir = dir + '/' + chosen_release
    out_wrapper = out_dir + '/scylla_wrapper'
    if os.path.exists(out_wrapper):
        # We already downloaded this version, nothing left to do.
        print(f'Already downloaded, in {out_dir}')
        return out_wrapper
    if not os.path.exists(out_dir):
        os.mkdir(out_dir)
    out_tar = out_dir + '/download.tgz'
    s3_download(s3, bucket, chosen, out_tar)
    subprocess.run(['tar', '--extract', '--file', out_tar,
        '--strip-components=1', '-C', out_dir])
    os.unlink(out_tar)
    with open(out_wrapper, 'w') as f:
        f.write(f'#!/bin/sh\nLD_LIBRARY_PATH={out_dir}/libreloc {out_dir}/libreloc/ld.so {out_dir}/libexec/scylla "$@"')
    os.chmod(out_wrapper, 0o755)
    print(f'Downloaded to {out_dir}')
    return out_wrapper
