#!/usr/bin/env python3

import argparse
import math
import os
import platform
import random
import shutil
import socket
import string
import subprocess
import sys
import time
import webbrowser
import zipfile

import distro
import requests
import yaml
from packaging.version import parse as parse_version
from tqdm import tqdm

SCHEMA_VERSION = "24"
CHUNK_SIZE = 4096

# Connector Package Version Bounds
PYMONGO_VERSION_BOUND = "<=4.3.3"
PSYCOPG2_VERSION_BOUND = "<=2.9.5"
BIGQUERY_VERSION_BOUND = "<=3.5.0"
SNOWFLAKE_VERSION_BOUND = "<=1.4.4"
PYARROW_VERSION_BOUND = "<=11.0.0"
AWS_WRANGLER_VERSION_BOUND = "<=2.19.0"
MYSQL_CLIENT_VERSION_BOUND = "<=2.1.1"
PYODBC_VERSION_BOUND = "<=4.0.35"

base_directory = os.path.join(os.environ["HOME"], ".aqueduct")
server_directory = os.path.join(os.environ["HOME"], ".aqueduct", "server")
ui_directory = os.path.join(os.environ["HOME"], ".aqueduct", "ui")

package_version = "0.2.6"
aws_credentials_path = os.path.join(os.environ["HOME"], ".aws", "credentials")

default_server_port = 8080

s3_server_prefix = (
    "https://aqueduct-ai.s3.us-east-2.amazonaws.com/assets/%s/server" % package_version
)
s3_ui_prefix = "https://aqueduct-ai.s3.us-east-2.amazonaws.com/assets/%s/ui" % package_version

login_url = "http://%s:%d/login?apiKey=%s"
welcome_message = """
***************************************************
Your API Key: %s

The Web UI and the backend server (v%s) are accessible at: http://%s:%d/login?apiKey=%s

Aqueduct collects non sensitive usage data to improve its services.
Please refer to https://docs.aqueducthq.com/usage for more details.
Usage data collection is enabled by default, and can be disabled via `aqueduct start --disable-usage-stats`.
***************************************************
"""

conda_cmd_prefix = "conda"
env_file_path = os.path.join(os.environ["HOME"], ".aqueduct", "server", "config", "env")


def update_config_yaml(file):
    s = string.ascii_uppercase + string.digits
    encryption_key = "".join(random.sample(s, 32))
    api_key = "".join(random.sample(s, 32))

    with open(file, "r") as sources:
        lines = sources.readlines()
    with open(file, "w") as sources:
        for line in lines:
            if "<BASE_PATH>" in line:
                sources.write(line.replace("<BASE_PATH>", server_directory))
            elif "<ENCRYPTION_KEY>" in line:
                sources.write(line.replace("<ENCRYPTION_KEY>", encryption_key))
            elif "<API_KEY>" in line:
                sources.write(line.replace("<API_KEY>", api_key))
            else:
                sources.write(line)
    print("Updated configurations.")


def execute_command(args, cwd=None):
    with subprocess.Popen(args, stdout=sys.stdout, stderr=sys.stderr, cwd=cwd) as proc:
        proc.communicate()
        if proc.returncode != 0:
            raise Exception("Error executing command: %s" % args)


def generate_version_file(file_path):
    with open(file_path, "w") as f:
        f.write(package_version)


# Returns a bool indicating whether we need to perform a version upgrade.
def require_update(file_path):
    if not os.path.isfile(file_path):
        return True
    with open(file_path, "r") as f:
        current_version = f.read()
        if parse_version(package_version) < parse_version(current_version):
            raise Exception(
                "Attempting to install an older version %s but found existing newer version %s"
                % (package_version, current_version)
            )
        elif package_version == current_version:
            return False
        else:
            return True


def update_executable_permissions():
    os.chmod(os.path.join(server_directory, "bin", "server"), 0o755)
    os.chmod(os.path.join(server_directory, "bin", "executor"), 0o755)
    os.chmod(os.path.join(server_directory, "bin", "migrator"), 0o755)


def is_tool(name):
    """Check whether `name` is on PATH and marked as executable."""
    # from whichcraft import which
    from shutil import which
    return which(name) is not None


# Retrive all conda environments. This does not need to be 100% accurate
# as we only care about aqueduct-internal ones.
#
# It runs `conda env list` under the hood, and we ignore any error
# when running the command.
def get_conda_environments():
    cmd_result = subprocess.run([conda_cmd_prefix, 'env', 'list'], stdout=subprocess.PIPE)
    if cmd_result.returncode == 0:
        results = set()
        for line in cmd_result.stdout.decode().split("\n"):
            # skip helper message lines like `# conda environments:`
            if not line.startswith('#'):
                results.add(line.split(' ')[0])
        return results
    # we ignore none-zero exit code and assuming it's due to the command not available.
    return set()


def update_base_conda_environments():
    conda_envs = get_conda_environments()
    for py_version in ["3.7", "3.8", "3.9", "3.10"]:
        env_name = f"aqueduct_python{py_version}"
        if env_name in conda_envs:
            print(f"Updating conda environment {env_name}")
            execute_command([
                conda_cmd_prefix,
                "run",
                "-n",
                env_name,
                "pip3",
                "install",
                "-q", # hide most pip outputs, which is noisy
                f"aqueduct-ml=={package_version}",
            ])


def _download_file(s3_url, f, prefix=None):
    response = requests.get(s3_url, stream=True)
    total_length = response.headers.get("content-length")

    if not total_length:  # content-length header was not included
        f.write(response.content)
    else:
        total_length = int(total_length)
        for data in tqdm(
            response.iter_content(chunk_size=CHUNK_SIZE),
            total=math.ceil(total_length / CHUNK_SIZE),
            unit="KB",
            unit_scale=(CHUNK_SIZE / 1024),
            colour="#002F5E",
            ncols=100,
            desc=prefix,
        ):
            f.write(data)


def download_server_binaries(architecture):
    print("Downloading server binaries...")
    with open(os.path.join(server_directory, "bin/server"), "wb") as f:
        _download_file(os.path.join(s3_server_prefix, f"bin/{architecture}/server"), f, "Server")
    with open(os.path.join(server_directory, "bin/executor"), "wb") as f:
        _download_file(
            os.path.join(s3_server_prefix, f"bin/{architecture}/executor"), f, "Executor"
        )
    with open(os.path.join(server_directory, "bin/migrator"), "wb") as f:
        _download_file(
            os.path.join(s3_server_prefix, f"bin/{architecture}/migrator"), f, "Migrator"
        )

    print("Downloading integration set up scripts...")
    with open(os.path.join(server_directory, "bin/start-function-executor.sh"), "wb") as f:
        _download_file(os.path.join(s3_server_prefix, f"bin/start-function-executor.sh"), f)
    with open(os.path.join(server_directory, "bin/dag.template"), "wb") as f:
        _download_file(os.path.join(s3_server_prefix, f"bin/dag.template"), f)
    with open(os.path.join(server_directory, "bin/install_sqlserver_ubuntu.sh"), "wb") as f:
        _download_file(os.path.join(s3_server_prefix, f"bin/install_sqlserver_ubuntu.sh"), f)
    print("Downloaded server binaries...")


def setup_server_binaries():
    server_bin_path = os.path.join(server_directory, "bin")
    shutil.rmtree(server_bin_path, ignore_errors=True)
    os.mkdir(server_bin_path)

    system = platform.system()
    arch = platform.machine()
    if system == "Linux" and arch == "x86_64":
        print("Operating system is Linux with architecture amd64.")
        download_server_binaries("linux_amd64")
    elif system == "Darwin" and arch == "x86_64":
        print("Operating system is Mac with architecture amd64.")
        download_server_binaries("darwin_amd64")
    elif system == "Darwin" and arch == "arm64":
        print("Operating system is Mac with architecture arm64.")
        download_server_binaries("darwin_arm64")
    else:
        raise Exception(
            "Unsupported operating system and architecture combination: %s, %s" % (system, arch)
        )


def update_ui_version():
    print("Updating UI version to %s..." % package_version)
    try:
        shutil.rmtree(ui_directory, ignore_errors=True)
        os.mkdir(ui_directory)
        generate_version_file(os.path.join(ui_directory, "__version__"))
        ui_zip_path = os.path.join(ui_directory, "ui.zip")
        with open(ui_zip_path, "wb") as f:
            s3_path = os.path.join(s3_ui_prefix, "default", "ui.zip")
            _download_file(s3_path, f, "UI")
        with zipfile.ZipFile(ui_zip_path, "r") as zip:
            zip.extractall(ui_directory)
        os.remove(ui_zip_path)
    except Exception as e:
        print(e)
        shutil.rmtree(ui_directory, ignore_errors=True)
        exit(1)


def update_server_version():
    print("Updating server version to %s..." % package_version)

    current_version = "0.0.0"
    version_file = os.path.join(server_directory, "__version__")
    if os.path.isfile(version_file):
        with open(version_file, "r") as f:
            current_version = f.read()
        os.remove(version_file)

    generate_version_file(version_file)

    setup_server_binaries()
    update_executable_permissions()

    execute_command(
        [
            os.path.join(server_directory, "bin", "migrator"),
            "--type",
            "sqlite",
            "goto",
            SCHEMA_VERSION,
        ]
    )

    if parse_version(current_version) < parse_version("0.1.0") and parse_version(package_version) >= parse_version("0.1.0"):
        # We add a couple new tables to the demo db for the v0.1.0 release.
        # If we want to add tables in other releases in the future, we should refactor each of them
        # into a helper function.
        data_script_path = os.path.join(server_directory, "db", "0.1.0", "create_tables.py")
        data_script_dir = os.path.dirname(data_script_path)
        if not os.path.isdir(data_script_dir):
            os.mkdir(data_script_dir)
        s3_path = ("https://aqueduct-ai.s3.us-east-2.amazonaws.com/assets/demo/0.1.0/create_tables.py")
        with open(data_script_path, "wb") as f:
            f.write(requests.get(s3_path).content)
        os.chmod(data_script_path, 0o755)
        subprocess.check_call([sys.executable, data_script_path])
        print("Successfully added new tables to the demo db as part of release v0.1.0.")


def update():
    if not os.path.isdir(base_directory):
        os.makedirs(base_directory)

    if not os.path.isdir(ui_directory) or require_update(os.path.join(ui_directory, "__version__")):
        update_ui_version()

    if not os.path.isdir(server_directory):
        try:
            directories = [
                server_directory,
                os.path.join(server_directory, "db"),
                os.path.join(server_directory, "storage"),
                os.path.join(server_directory, "storage", "operators"),
                os.path.join(server_directory, "vault"),
                os.path.join(server_directory, "bin"),
                os.path.join(server_directory, "config"),
                os.path.join(server_directory, "logs"),
            ]

            for directory in directories:
                os.mkdir(directory)

            with open(os.path.join(server_directory, "config/config.yml"), "wb") as f:
                f.write(requests.get(os.path.join(s3_server_prefix, "config/config.yml")).content)

            update_config_yaml(os.path.join(server_directory, "config", "config.yml"))

            with open(os.path.join(server_directory, "db/demo.db"), "wb") as f:
                f.write(requests.get(os.path.join(s3_server_prefix, "db/demo.db")).content)

            print("Finished initializing Aqueduct base directory.")
        except Exception as e:
            print(e)
            shutil.rmtree(server_directory, ignore_errors=True)
            exit(1)

    # Ensure that the preview outputs directory always exists, and always starts off empty.
    # Contents from previous server sessions will be cleared.
    preview_outputs_directory = os.path.join(server_directory, "storage", "preview")
    if os.path.isdir(preview_outputs_directory):
        shutil.rmtree(preview_outputs_directory)
    os.mkdir(preview_outputs_directory)

    server_version_file = os.path.join(server_directory, "__version__")
    if require_update(server_version_file):
        try:
            update_server_version()
            if is_tool(conda_cmd_prefix):
                update_base_conda_environments()
        except Exception as e:
            print(e)
            if os.path.isfile(server_version_file):
                os.remove(server_version_file)
            exit(1)


def execute_command(args, cwd=None):
    with subprocess.Popen(args, stdout=sys.stdout, stderr=sys.stderr, cwd=cwd) as proc:
        proc.communicate()
        if proc.returncode != 0:
            raise Exception("Error executing command: %s" % args)


def execute_command_nonblocking(args, cwd=None):
    return subprocess.Popen(args, stdout=sys.stdout, stderr=sys.stderr, cwd=cwd)


def get_address(expose):
    if not expose:
        expose_ip = "localhost"
    else:
        try:
            ec2_ip = requests.get(
                "http://169.254.169.254/latest/meta-data/public-ipv4", timeout=0.25
            )
            if ec2_ip.status_code != 404: # User is in EC2 instance.
                expose_ip = ec2_ip.content.decode("utf-8")
            else:  
                # Assume is Google Cloud
                metadata_flavor = {'Metadata-Flavor': 'Google'}
                gcp_ip = requests.get('http://169.254.169.254/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip', headers = metadata_flavor, timeout=0.25)
                if gcp_ip.status_code != 404:
                    expose_ip = gcp_ip.text
                else:
                    # Assume is Azure
                    azure_ip = requests.get('http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress?api-version=2017-08-01&format=text', headers = {'Metadata': 'true'}, timeout=0.25)
                    if azure_ip.status_code != 404:
                        expose_ip = azure_ip.text
                    else:
                        # Default
                        expose_ip = "<IP_ADDRESS>"
        except:  # If you're not running on EC2, this will return an error.
            expose_ip = "<IP_ADDRESS>"
    
    return expose_ip

def generate_welcome_message(addr, port):
    apikey = get_apikey()

    return (
        welcome_message % (apikey, package_version, addr, port, apikey),
        login_url % (addr, port, apikey),
    )


def is_port_in_use(port: int) -> bool:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        return s.connect_ex(("localhost", port)) == 0


def cache_env(env):
    if env:
        with open(env_file_path, "w") as f:
            f.write(env)


def start(addr, expose, port, verbose, env, disable_usage_stats):
    update()

    if port is None:
        server_port = default_server_port
        while is_port_in_use(server_port):
            server_port += 1
        if not server_port == default_server_port:
            print(
                "Default port %d is occupied. Next available port is %d"
                % (default_server_port, server_port)
            )
    else:
        server_port = int(port)
        print("Server will use the user-specified port %d" % server_port)

    cache_env(env)

    command = [
        os.path.join(server_directory, "bin", "server"),
        "--config",
        os.path.join(server_directory, "config", "config.yml"),
        "--port",
        str(server_port),
        "--external-ip",
        addr,
    ]

    if expose:
        command.append("--expose")
    
    if verbose:
        command.append("--verbose")

    if disable_usage_stats:
        command.append("--disable-usage-stats")

    popen_handle = execute_command_nonblocking(command)
    return popen_handle, server_port

def install_mongodb():
    execute_command([sys.executable, "-m", "pip", "install", "pymongo%s" % PYMONGO_VERSION_BOUND])

def install_postgres():
    execute_command([sys.executable, "-m", "pip", "install", "psycopg2-binary%s" % PSYCOPG2_VERSION_BOUND])

def install_bigquery():
    execute_command([sys.executable, "-m", "pip", "install", "google-cloud-bigquery%s" % BIGQUERY_VERSION_BOUND])

def install_snowflake():
    execute_command([sys.executable, "-m", "pip", "install", "snowflake-sqlalchemy%s" % SNOWFLAKE_VERSION_BOUND])

def install_s3():
    execute_command([sys.executable, "-m", "pip", "install", "pyarrow%s" % PYARROW_VERSION_BOUND])

def install_athena():
    execute_command([sys.executable, "-m", "pip", "install", "awswrangler%s" % AWS_WRANGLER_VERSION_BOUND])

def install_mysql():
    system = platform.system()
    if system == "Linux":
        if distro.id() == "ubuntu" or distro.id() == "debian":
            execute_command(
                [
                    "sudo",
                    "apt-get",
                    "install",
                    "-y",
                    "python3-dev",
                    "default-libmysqlclient-dev",
                    "build-essential",
                ]
            )
        elif distro.id() == "centos" or distro.id() == "rhel":
            execute_command(["sudo", "yum", "install", "-y", "python3-devel", "mysql-devel"])
        else:
            print("Unsupported distribution:", distro.id())
    elif system == "Darwin":
        cmd = ["brew", "install", "mysql"]
        architecture = subprocess.Popen(["which", "-a", "brew"], stdout=subprocess.PIPE ).communicate()[0]
        if architecture.startswith(b"/opt/homebrew"): # Using arm verison of brew
            cmd = ["arch", "-arm64", *cmd]
        execute_command(cmd)
    else:
        print("Unsupported operating system:", system)

    execute_command([sys.executable, "-m", "pip", "install", "mysqlclient%s" % MYSQL_CLIENT_VERSION_BOUND])


def install_sqlserver():
    system = platform.system()
    if system == "Linux":
        if distro.id() == "ubuntu":
            execute_command(
                ["bash", os.path.join(server_directory, "bin", "install_sqlserver_ubuntu.sh")]
            )
        else:
            print("Unsupported distribution:", distro.id())
    elif system == "Darwin":
        execute_command(
            [
                "brew",
                "tap",
                "microsoft/mssql-release",
                "https://github.com/Microsoft/homebrew-mssql-release",
            ]
        )
        execute_command(["brew", "update"])
        execute_command(
            [
                "HOMEBREW_NO_ENV_FILTERING=1",
                "ACCEPT_EULA=Y",
                "brew",
                "install",
                "msodbcsql17",
                "mssql-tools",
            ]
        )
    else:
        print("Unsupported operating system:", system)

    execute_command([sys.executable, "-m", "pip", "install", "pyodbc%s" % PYODBC_VERSION_BOUND])


def install(system):
    if system == "postgres" or system == "redshift":
        install_postgres()
    elif system == "bigquery":
        install_bigquery()
    elif system == "snowflake":
        install_snowflake()
    elif system == "s3":
        install_s3()
    elif system == "athena":
        install_athena()
    elif system == "mysql" or system == "mariadb":
        install_mysql()
    elif system == "sqlserver" or system == "azuresql":
        install_sqlserver()
    elif system == "mongodb":
        install_mongodb()
    else:
        raise Exception("Unsupported system: %s" % system)


def get_apikey():
    config_file = os.path.join(server_directory, "config", "config.yml")
    with open(config_file, "r") as f:
        try:
            return yaml.safe_load(f)["apiKey"]
        except yaml.YAMLError as exc:
            print(exc)
            exit(1)


def apikey():
    print(get_apikey())


def clear():
    shutil.rmtree(base_directory, ignore_errors=True)


def version():
    print(package_version)


def read_config():
    with open(os.path.join(server_directory, "config", "config.yml"), "r") as f:
        config = yaml.safe_load(f)
    return config


def write_config(config):
    with open(os.path.join(server_directory, "config", "config.yml"), "w") as f:
        yaml.dump(config, f)


def use_local_storage(path):
    config = read_config()

    path = os.path.join(server_directory, path)

    file_config = {"directory": path}
    config["storageConfig"] = {
        "type": "file",
        "fileConfig": file_config,
    }

    write_config(config)


def use_s3_storage(region, bucket, creds_path, creds_profile):
    if not bucket.startswith("s3://"):
        print("S3 path is malformed, it should be of the form s3://")
        sys.exit(1)

    config = read_config()

    s3_config = {
        "region": region,
        "bucket": bucket,
        "credentialsPath": creds_path,
        "credentialsProfile": creds_profile,
    }
    config["storageConfig"] = {
        "type": "s3",
        "s3Config": s3_config,
    }

    write_config(config)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="The Aqueduct CLI")
    subparsers = parser.add_subparsers(dest="command")

    start_args = subparsers.add_parser(
        "start",
        help="""This starts the Aqueduct server and the UI in a blocking
                               fashion. To background the process run aqueduct start &.

                               Add --expose <IP_ADDRESS> to access the Aqueduct service from
                               an external server, where <IP_ADDRESS> is the
                               public IP of the server running the Aqueduct service.
                               """,
    )
    start_args.add_argument(
        "--expose",
        default=False,
        action="store_true",
        help="Use this option to expose the server to the public.",
    )
    start_args.add_argument(
        "--verbose",
        default=False,
        action="store_true",
        help="""If set to true, all log messages will be printed on stdout. By default, only error
                and warning-level logs are routed to stdout. All log messages can be found at
                $HOME/.aqueduct/server/logs/server""",
    )
    start_args.add_argument(
        "--port", dest="port", help="Specify the port on which the Aqueduct server runs."
    )
    start_args.add_argument(
        "--env",
        dest="env",
        help="Specify the environment in which the Aqueduct server is operating.",
    )
    start_args.add_argument(
        "--disable-usage-stats",
        default=False,
        action="store_true",
        dest="disable_usage_stats",
        help="If set to true, usage statistics reporting will be disabled",
    )

    install_args = subparsers.add_parser(
        "install",
        help="""Install the required library dependencies for
                             an Aqueduct connector to a third-party system.""",
    )
    install_args.add_argument(
        "system",
        nargs=1,
        help="Supported integrations: postgres, redshift, mysql, mariadb, sqlserver, azuresql, s3, athena, snowflake, bigquery.",
    )

    apikey_args = subparsers.add_parser("apikey", help="Display your Aqueduct API key.")
    clear_args = subparsers.add_parser("clear", help="Erase your Aqueduct installation.")
    version_args = subparsers.add_parser("version", help="Retrieve the package version number.")

    storage_args = subparsers.add_parser(
        "storage",
        help="""This changes the storage location for any new workflows created.
                               The change will take affect once you restart the Aqueduct server.
                               We are currently working on adding support for modifying the storage
                               location of existing workflows.
                               """,
    )
    storage_args.add_argument(
        "--use",
        dest="storage_use",
        choices=["local", "s3"],
        required=True,
        help="The following storage locations are supported: local, s3",
    )
    storage_args.add_argument(
        "--path",
        dest="storage_path",
        required=True,
        help="""For local storage this is the filepath of the storage directory.
                                This should be relative to the Aqueduct installation path.
                                For S3 storage this is the S3 path, which should be of the form:
                                s3://bucket/path/to/folder
                                """,
    )
    storage_args.add_argument(
        "--region", dest="storage_s3_region", help="The AWS S3 region where the bucket is located."
    )
    storage_args.add_argument(
        "--credentials",
        dest="storage_s3_creds",
        default=aws_credentials_path,
        help="""The filepath to the AWS credentials to use.
                                It defaults to ~/.aws/credentials""",
    )
    storage_args.add_argument(
        "--profile",
        dest="storage_s3_profile",
        default="default",
        help="""The AWS credentials profile to use. It uses default 
                                if none is provided.""",
    )

    args = parser.parse_args()
    sysargs = sys.argv

    if args.command == "start":
        try:
            addr = get_address(args.expose)
            popen_handle, server_port = start(
                addr,
                args.expose,
                args.port,
                args.verbose,
                args.env,
                args.disable_usage_stats,
            )
            time.sleep(1)
            terminated = popen_handle.poll()
            if terminated:
                print("Server terminated due to an error.")
            else:
                welcome_message, url = generate_welcome_message(addr, server_port)
                print(welcome_message)

                if not args.expose:
                    webbrowser.open(url)
                popen_handle.wait()
        except (Exception, KeyboardInterrupt) as e:
            print(e)
            print("\nTerminating Aqueduct service...")
            popen_handle.kill()
            print("Aqueduct service successfully terminated.")
    elif args.command == "server":
        print(
            "aqueduct ui and aqueduct server have been deprecated; please use aqueduct start to run both the UI and backend servers"
        )
    elif args.command == "install":
        install(args.system[0])  # argparse makes this an array so only pass in value [0].
    elif args.command == "ui":
        print(
            "aqueduct ui and aqueduct server have been deprecated; please use aqueduct start to run both the UI and backend servers"
        )
    elif args.command == "apikey":
        apikey()
    elif args.command == "clear":
        clear()
    elif args.command == "version":
        version()
    elif args.command == "storage":
        if args.storage_use == "local":
            # Ensure that S3 args are not provided for local storage
            s3_args = ["--region", "--credentials", "--profile"]
            for s3_arg in s3_args:
                if s3_arg in sysargs:
                    print("{} should not be used with local storage".format(s3_arg))
                    sys.exit(1)
            use_local_storage(args.storage_path)
        elif args.storage_use == "s3":
            # Ensure that required S3 args are provided
            if "--region" not in sysargs:
                print("--region is required when using S3 storage")
                sys.exit(1)
            use_s3_storage(
                args.storage_s3_region,
                args.storage_path,
                args.storage_s3_creds,
                args.storage_s3_profile,
            )
        else:
            print("Unsupported storage type: ", args.storage_use)
            sys.exit(1)
    elif args.command is None:
        parser.print_help()
    else:
        print("Unsupported command:", args.command)
        sys.exit(1)
