#!/usr/bin/env python3
# pylint: disable=C0103
"""
An operation that mutates a build by uploading to Azure,
extending the meta.json with the Azure image name.
"""
# NOTE: PYTHONUNBUFFERED is set in the entrypoint for unbuffered output

import argparse
import logging as log
import os
import shutil
import sys
import urllib.parse

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from cosalib.build import _Build

# pylint: disable=C0413
from cosalib.cmdlib import (
    get_basearch,
    run_verbose,
    sha256sum_file)

# Identifier for azure platform
PLATFORM_AZURE = 'azure'


class Build(_Build):
    """
    Azure implementation of Build.
    """

    def __init__(self, build_dir, build, tmpbuilddir):
        self.tmpbuilddir = tmpbuilddir
        _Build.__init__(self, build_dir, build)

    def _build_artifacts(self, *args, **kwargs):
        """
        Implements the building of artifacts. Walk the build root and
        prepare a list of files in it.
        :param args: All non-keyword arguments
        :type args: list
        :param kwargs: All keyword arguments
        :type kwargs: dict
        """

        if os.path.isdir(self.tmpbuilddir):
            shutil.rmtree(self.tmpbuilddir)
        os.mkdir(self.tmpbuilddir)

        # Name the base build and tarball name
        base_name = self.meta['name']
        azure_nv = f'{base_name}-{self.build_id}'

        # Used in referencing
        basearch = get_basearch()
        self.azure_vhd_name = f'{azure_nv}-azure.{basearch}.vhd'

        # Generate path locations
        img_qemu = os.path.join(
            self.build_dir, self.meta['images']['qemu']['path'])

        tmp_img_azure = os.path.join(self.tmpbuilddir, (azure_nv + '.qcow2'))
        tmp_img_azure_vhd = os.path.join(self.tmpbuilddir, self.azure_vhd_name)

        # Execute system commands
        run_verbose(['/usr/lib/coreos-assembler/gf-platformid',
                     img_qemu, tmp_img_azure, PLATFORM_AZURE])
        # Convert the qcow2 to a raw image
        # See: https://docs.openstack.org/image-guide/convert-images.html
        run_verbose(['qemu-img', 'convert', '-f', 'qcow2', '-O', 'vpc',
                    tmp_img_azure, tmp_img_azure_vhd])

        # place the VHD into is final place
        final_vhd = os.path.basename(tmp_img_azure_vhd)
        final_vhd_path = f"{self.build_dir}/{final_vhd}"
        os.rename(tmp_img_azure_vhd, final_vhd_path)
        shutil.rmtree(self.tmpbuilddir)

        # TODO: This can be pulled out into a _Build method.
        fsize = os.stat(final_vhd_path).st_size
        log.debug(" * calculating checksum")
        self._found_files[final_vhd_path] = {
            "local_path": final_vhd_path,
            "path": final_vhd,
            "size": int(fsize)
        }
        log.debug(
            " * size is %s",
            self._found_files[final_vhd_path]["size"])

        # Update the metadata with info about the built image
        checksum = sha256sum_file(final_vhd_path)
        size = os.path.getsize(final_vhd_path)

        self.meta['images'].update({
            'azure': {
                'path': build.azure_vhd_name,
                'sha256': checksum,
                'size': size
            }
        })
        self.meta_write()  # update build metadata


def cli():
    """
    Parse args and dispatch
    """
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--auth', help='Path to Azure auth file',
        default=os.environ.get("AZURE_AUTH"))
    parser.add_argument('--build', help='Build ID')
    parser.add_argument(
        '--container', help='Storage location to write to',
        default=os.environ.get("AZURE_CONTAINER"))
    parser.add_argument(
        '--force', help='Replace existing images and upload',
        action='store_true',
        default=bool(os.environ.get('AZURE_FORCE', False)))
    parser.add_argument('--location', help='Azure location (default westus)',
        default=os.environ.get("AZURE_LOCATION", "westus"))
    parser.add_argument(
        '--profile', help='Path to Azure profile',
        default=os.environ.get('AZURE_PROFILE'))
    parser.add_argument(
        '--resource-group', help='Resource group',
        default=os.environ.get('AZURE_RESOURCE_GROUP'))
    parser.add_argument(
        '--storage-account', help='Storage account',
        default=os.environ.get('AZURE_STORAGE_ACCOUNT'))
    parser.add_argument("--log-level", help="ore log level")
    parser.add_argument("--upload", action='store_true', default=False,
                        help="Upload to Azure")
    args = parser.parse_args()

    if args.upload:
        # Argument checks for environment strings that are required
        arg_exp_str = "parameter '--{}' or envVar '{}' must be defined when uploading"
        for attr in ['auth', 'container', 'profile',
                     'resource_group', 'storage_account']:
            if getattr(args, attr) is None:
                raise Exception(arg_exp_str.format(attr, 'AZURE_' + attr.upper()))

    return args


def run_ore(args, build):
    """
    Execute ore to upload the vhd image in blob format
    See:
      - https://github.com/coreos/mantle/#azure
      - https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction
    TODO: Move to using an _Upload subclass.
    :param args: The command line arguments
    :type args: argparse.Namespace
    :param build: Build instance to use
    :type build: Build
    """
    azure_vhd_path = os.path.join(build.build_dir, build.azure_vhd_name)
    ore_upload_args = ['ore']
    if args.log_level:
        ore_upload_args.extend(['--log-level', args.log_level])
    ore_upload_args.extend([
        'azure', 'upload-blob-arm',
        '--azure-auth', args.auth,
        '--azure-location', args.location,
        '--azure-profile', args.profile,
        '--blob-name', build.azure_vhd_name,
        '--container', args.container,
        '--file', azure_vhd_path,
        '--resource-group', args.resource_group,
        '--storage-account', args.storage_account])
    if args.force:
        ore_upload_args.append('--overwrite')
    run_verbose(ore_upload_args)

    url_path = urllib.parse.quote((
        f"{args.storage_account}.blob.core.windows.net/"
        f"{args.container}/{build.azure_vhd_name}"
    ))
    build.meta['azure'] = {
        'image': build.azure_vhd_name,
        'url': f"https://{url_path}",
    }
    build.meta_write()  # update build metadata


if __name__ == '__main__':
    # parse provided args
    args = cli()

    # Identify the builds
    build = Build(
        os.path.join('builds'),
        args.build or 'latest',
        tmpbuilddir=os.path.abspath('tmp/buildpost-azure'))

    # Build the image artifacts
    if 'azure' in build.meta['images'] and not args.force:
        print('Azure image is already built. Re-using that image. '
              'Use --force to override!')
    else:
        build.build_artifacts(cli_args=args)

    # Do the upload if asked
    if args.upload:
        run_ore(args, build)
    else:
        print('--upload not passed. Skipping image upload to cloud')
    print('buildextend-azure complete!')
