import time
import os
import sys
import shutil
import json
import argparse
from zipfile import ZipFile
from contextlib import contextmanager
from datetime import datetime
from Tests.private_build.upload_packs_private import download_and_extract_index, update_index_with_priced_packs, \
    extract_packs_artifacts
from Tests.Marketplace.marketplace_services import init_storage_client
from Tests.scripts.utils.log_util import install_logging
from Tests.scripts.utils import logging_wrapper as logging

MAX_SECONDS_TO_WAIT_FOR_LOCK = 600
LOCK_FILE_PATH = 'lock.txt'


@contextmanager
def lock_and_unlock_dummy_index(public_storage_bucket, dummy_index_lock_path):
    try:
        acquire_dummy_index_lock(public_storage_bucket, dummy_index_lock_path)
        yield
    except Exception:
        logging.exception("Error in dummy index lock context manager.")
    finally:
        release_dummy_index_lock(public_storage_bucket, dummy_index_lock_path)


def change_pack_price_to_zero(path_to_pack_metadata):
    with open(path_to_pack_metadata, 'r') as pack_metadata_file:
        pack_metadata = json.load(pack_metadata_file)

    pack_metadata['price'] = 0
    with open(path_to_pack_metadata, 'w') as pack_metadata_file:
        json.dump(pack_metadata, pack_metadata_file, indent=4)


def change_packs_price_to_zero(public_index_folder_path):
    paths_to_packs_in_merged_index = [pack_dir.path for pack_dir in os.scandir(public_index_folder_path) if
                                      pack_dir.is_dir()]
    for path_to_pack in paths_to_packs_in_merged_index:
        path_to_pack_metadata = os.path.join(path_to_pack, 'metadata.json')
        change_pack_price_to_zero(path_to_pack_metadata)


def merge_private_index_into_public_index(public_index_folder_path, private_index_folder_path):
    packs_in_private_index = [pack_dir.name for pack_dir in os.scandir(private_index_folder_path) if pack_dir.is_dir()]
    for pack_name in packs_in_private_index:
        path_to_pack_in_private_index = os.path.join(private_index_folder_path, pack_name)
        path_to_pack_in_public_index = os.path.join(public_index_folder_path, pack_name)
        shutil.copy(path_to_pack_in_private_index, path_to_pack_in_public_index)


def upload_modified_index(public_index_folder_path, extract_destination_path, public_ci_dummy_index_blob, build_number,
                          private_packs):
    """Upload updated index zip to cloud storage.

    Args:
        public_index_folder_path (str): public index folder full path.
        extract_destination_path (str): extract folder full path.
        public_ci_dummy_index_blob (Blob): google cloud storage object that represents the dummy index.zip blob.
        build_number (str): circleCI build number, used as an index revision.
        private_packs (list): List of private packs and their price.

    """
    with open(os.path.join(public_index_folder_path, "index.json"), "w+") as index_file:
        for private_pack in private_packs:
            private_pack['price'] = 0
        index = {
            'revision': build_number,
            'modified': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),
            'packs': private_packs
        }
        json.dump(index, index_file, indent=4)

    index_zip_name = os.path.basename(public_index_folder_path)
    index_zip_path = shutil.make_archive(base_name=public_index_folder_path, format="zip",
                                         root_dir=extract_destination_path, base_dir=index_zip_name)
    try:
        public_ci_dummy_index_blob.reload()
        public_ci_dummy_index_blob.cache_control = "no-cache,max-age=0"  # disabling caching for index blob
        public_ci_dummy_index_blob.upload_from_filename(index_zip_path)

        logging.success("Finished uploading index.zip to storage.")
    except Exception:
        logging.exception("Failed in uploading index. Mismatch in index file generation.")
        sys.exit(1)
    finally:
        shutil.rmtree(public_index_folder_path)


def option_handler():
    """Validates and parses script arguments.

    Returns:
        Namespace: Parsed arguments object.

    """
    parser = argparse.ArgumentParser(description="Store packs in cloud storage.")
    # disable-secrets-detection-start
    parser.add_argument('-b', '--public_bucket_name', help="CI public bucket name", required=True)
    parser.add_argument('-pb', '--private_bucket_name', help="CI private bucket name", required=True)
    parser.add_argument('-s', '--service_account',
                        help=("Path to gcloud service account, is for circleCI usage. "
                              "For local development use your personal account and "
                              "authenticate using Google Cloud SDK by running: "
                              "`gcloud auth application-default login` and leave this parameter blank. "
                              "For more information go to: "
                              "https://googleapis.dev/python/google-api-core/latest/auth.html"),
                        required=False)
    parser.add_argument('-n', '--ci_build_number',
                        help="CircleCi build number (will be used as hash revision at index file)", required=True)
    parser.add_argument('-e', '--extract_public_index_path', help="Full path of folder to extract the public index",
                        required=True)
    parser.add_argument('-sb', '--storage_base_path', help="Storage base path of the directory to upload to.",
                        required=False)
    parser.add_argument('-p', '--pack_name', help="Modified pack to upload to gcs.")
    parser.add_argument('-a', '--artifacts_path', help="The full path of packs artifacts", required=True)
    parser.add_argument('-ea', '--extract_artifacts_path', help="Full path of folder to extract wanted packs",
                        required=True)
    parser.add_argument('-di', '--dummy_index_dir_path', help="Full path to the dummy index in the private CI bucket",
                        required=True)

    # disable-secrets-detection-end
    return parser.parse_args()


def is_dummy_index_locked(public_storage_bucket, dummy_index_lock_path):
    dummy_index_lock_blob = public_storage_bucket.blob(dummy_index_lock_path)
    return dummy_index_lock_blob.exists()


def lock_dummy_index(public_storage_bucket, dummy_index_lock_path):
    dummy_index_lock_blob = public_storage_bucket.blob(dummy_index_lock_path)
    with open(LOCK_FILE_PATH, 'w') as lock_file:
        lock_file.write('locked')

    with open(LOCK_FILE_PATH, 'rb') as lock_file:
        dummy_index_lock_blob.upload_from_file(lock_file)


def acquire_dummy_index_lock(public_storage_bucket, dummy_index_lock_path):
    total_seconds_waited = 0
    while is_dummy_index_locked(public_storage_bucket, dummy_index_lock_path):
        if total_seconds_waited >= MAX_SECONDS_TO_WAIT_FOR_LOCK:
            logging.critical("Error: Failed too long to acquire lock, exceeded max wait time.")
            sys.exit(1)

        if total_seconds_waited % 60 == 0:
            # Printing a message every minute to keep the machine from dying due to no output
            logging.info("Waiting to acquire lock.")

        total_seconds_waited += 10
        time.sleep(10)

    lock_dummy_index(public_storage_bucket, dummy_index_lock_path)


def release_dummy_index_lock(public_storage_bucket, dummy_index_lock_path):
    dummy_index_lock_blob = public_storage_bucket.blob(dummy_index_lock_path)
    dummy_index_lock_blob.delete()
    os.remove(LOCK_FILE_PATH)


def add_private_packs_from_dummy_index(private_packs, dummy_index_blob):
    downloaded_dummy_index_path = 'current_dummy_index.zip'
    extracted_dummy_index_path = 'dummy_index'
    dummy_index_json_path = os.path.join(extracted_dummy_index_path, 'index', 'index.json')
    dummy_index_blob.download_to_filename(downloaded_dummy_index_path)
    os.mkdir(extracted_dummy_index_path)
    if os.path.exists(downloaded_dummy_index_path):
        with ZipFile(downloaded_dummy_index_path, 'r') as index_zip:
            index_zip.extractall(extracted_dummy_index_path)

    with open(dummy_index_json_path) as index_file:
        index_json = json.load(index_file)
        packs_from_dummy_index = index_json.get('packs', [])
        for pack in private_packs:
            is_pack_in_dummy_index = any(
                [pack['id'] == dummy_index_pack['id'] for dummy_index_pack in packs_from_dummy_index])
            if not is_pack_in_dummy_index:
                packs_from_dummy_index.append(pack)

    os.remove(downloaded_dummy_index_path)
    shutil.rmtree(extracted_dummy_index_path)
    return packs_from_dummy_index


def main():
    install_logging('prepare_public_index_for_private_testing.log', logger=logging)
    upload_config = option_handler()
    service_account = upload_config.service_account
    build_number = upload_config.ci_build_number
    public_bucket_name = upload_config.public_bucket_name
    private_bucket_name = upload_config.private_bucket_name
    storage_base_path = upload_config.storage_base_path
    extract_public_index_path = upload_config.extract_public_index_path
    changed_pack = upload_config.pack_name
    extract_destination_path = upload_config.extract_artifacts_path
    packs_artifacts_path = upload_config.artifacts_path
    dummy_index_dir_path = upload_config.dummy_index_dir_path
    dummy_index_path = os.path.join(dummy_index_dir_path, 'index.zip')
    dummy_index_lock_path = os.path.join(dummy_index_dir_path, 'lock.txt')

    storage_client = init_storage_client(service_account)
    public_storage_bucket = storage_client.bucket(public_bucket_name)
    private_storage_bucket = storage_client.bucket(private_bucket_name)

    dummy_index_blob = public_storage_bucket.blob(dummy_index_path)

    with lock_and_unlock_dummy_index(public_storage_bucket, dummy_index_lock_path):

        extract_packs_artifacts(packs_artifacts_path, extract_destination_path)
        public_index_folder_path, public_index_blob, _ = download_and_extract_index(public_storage_bucket,
                                                                                    extract_public_index_path, storage_base_path)

        # In order for the packs to be downloaded successfully, their price has to be 0
        change_packs_price_to_zero(public_index_folder_path)

        private_packs, private_index_path, private_index_blob = update_index_with_priced_packs(private_storage_bucket,
                                                                                               extract_destination_path,
                                                                                               public_index_folder_path,
                                                                                               changed_pack, True,
                                                                                               storage_base_path)
        private_packs = add_private_packs_from_dummy_index(private_packs, dummy_index_blob)
        upload_modified_index(public_index_folder_path, extract_public_index_path, dummy_index_blob, build_number,
                              private_packs)


if __name__ == '__main__':
    main()

[((78, 21, 78, 63), 'os.path.basename', 'os.path.basename', ({(78, 38, 78, 62): 'public_index_folder_path'}, {}), '(public_index_folder_path)', False, 'import os\n'), ((79, 21, 80, 100), 'shutil.make_archive', 'shutil.make_archive', (), '', False, 'import shutil\n'), ((101, 13, 101, 81), 'argparse.ArgumentParser', 'argparse.ArgumentParser', (), '', False, 'import argparse\n'), ((164, 4, 164, 29), 'os.remove', 'os.remove', ({(164, 14, 164, 28): 'LOCK_FILE_PATH'}, {}), '(LOCK_FILE_PATH)', False, 'import os\n'), ((170, 28, 170, 91), 'os.path.join', 'os.path.join', ({(170, 41, 170, 67): 'extracted_dummy_index_path', (170, 69, 170, 76): '"""index"""', (170, 78, 170, 90): '"""index.json"""'}, {}), "(extracted_dummy_index_path, 'index', 'index.json')", False, 'import os\n'), ((172, 4, 172, 40), 'os.mkdir', 'os.mkdir', ({(172, 13, 172, 39): 'extracted_dummy_index_path'}, {}), '(extracted_dummy_index_path)', False, 'import os\n'), ((173, 7, 173, 50), 'os.path.exists', 'os.path.exists', ({(173, 22, 173, 49): 'downloaded_dummy_index_path'}, {}), '(downloaded_dummy_index_path)', False, 'import os\n'), ((186, 4, 186, 42), 'os.remove', 'os.remove', ({(186, 14, 186, 41): 'downloaded_dummy_index_path'}, {}), '(downloaded_dummy_index_path)', False, 'import os\n'), ((187, 4, 187, 45), 'shutil.rmtree', 'shutil.rmtree', ({(187, 18, 187, 44): 'extracted_dummy_index_path'}, {}), '(extracted_dummy_index_path)', False, 'import shutil\n'), ((192, 4, 192, 83), 'Tests.scripts.utils.log_util.install_logging', 'install_logging', (), '', False, 'from Tests.scripts.utils.log_util import install_logging\n'), ((204, 23, 204, 70), 'os.path.join', 'os.path.join', ({(204, 36, 204, 56): 'dummy_index_dir_path', (204, 58, 204, 69): '"""index.zip"""'}, {}), "(dummy_index_dir_path, 'index.zip')", False, 'import os\n'), ((205, 28, 205, 74), 'os.path.join', 'os.path.join', ({(205, 41, 205, 61): 'dummy_index_dir_path', (205, 63, 205, 73): '"""lock.txt"""'}, {}), "(dummy_index_dir_path, 'lock.txt')", False, 'import os\n'), ((207, 21, 207, 57), 'Tests.Marketplace.marketplace_services.init_storage_client', 'init_storage_client', ({(207, 41, 207, 56): 'service_account'}, {}), '(service_account)', False, 'from Tests.Marketplace.marketplace_services import init_storage_client\n'), ((33, 24, 33, 53), 'json.load', 'json.load', ({(33, 34, 33, 52): 'pack_metadata_file'}, {}), '(pack_metadata_file)', False, 'import json\n'), ((37, 8, 37, 62), 'json.dump', 'json.dump', (), '', False, 'import json\n'), ((44, 32, 44, 75), 'os.path.join', 'os.path.join', ({(44, 45, 44, 57): 'path_to_pack', (44, 59, 44, 74): '"""metadata.json"""'}, {}), "(path_to_pack, 'metadata.json')", False, 'import os\n'), ((51, 40, 51, 90), 'os.path.join', 'os.path.join', ({(51, 53, 51, 78): 'private_index_folder_path', (51, 80, 51, 89): 'pack_name'}, {}), '(private_index_folder_path, pack_name)', False, 'import os\n'), ((52, 39, 52, 88), 'os.path.join', 'os.path.join', ({(52, 52, 52, 76): 'public_index_folder_path', (52, 78, 52, 87): 'pack_name'}, {}), '(public_index_folder_path, pack_name)', False, 'import os\n'), ((53, 8, 53, 80), 'shutil.copy', 'shutil.copy', ({(53, 20, 53, 49): 'path_to_pack_in_private_index', (53, 51, 53, 79): 'path_to_pack_in_public_index'}, {}), '(path_to_pack_in_private_index, path_to_pack_in_public_index)', False, 'import shutil\n'), ((76, 8, 76, 46), 'json.dump', 'json.dump', (), '', False, 'import json\n'), ((86, 8, 86, 67), 'Tests.scripts.utils.logging_wrapper.success', 'logging.success', ({(86, 24, 86, 66): '"""Finished uploading index.zip to storage."""'}, {}), "('Finished uploading index.zip to storage.')", True, 'from Tests.scripts.utils import logging_wrapper as logging\n'), ((91, 8, 91, 47), 'shutil.rmtree', 'shutil.rmtree', ({(91, 22, 91, 46): 'public_index_folder_path'}, {}), '(public_index_folder_path)', False, 'import shutil\n'), ((156, 8, 156, 22), 'time.sleep', 'time.sleep', ({(156, 19, 156, 21): '(10)'}, {}), '(10)', False, 'import time\n'), ((178, 21, 178, 42), 'json.load', 'json.load', ({(178, 31, 178, 41): 'index_file'}, {}), '(index_file)', False, 'import json\n'), ((215, 8, 215, 79), 'Tests.private_build.upload_packs_private.extract_packs_artifacts', 'extract_packs_artifacts', ({(215, 32, 215, 52): 'packs_artifacts_path', (215, 54, 215, 78): 'extract_destination_path'}, {}), '(packs_artifacts_path, extract_destination_path)', False, 'from Tests.private_build.upload_packs_private import download_and_extract_index, update_index_with_priced_packs, extract_packs_artifacts\n'), ((216, 57, 217, 129), 'Tests.private_build.upload_packs_private.download_and_extract_index', 'download_and_extract_index', ({(216, 84, 216, 105): 'public_storage_bucket', (217, 84, 217, 109): 'extract_public_index_path', (217, 111, 217, 128): 'storage_base_path'}, {}), '(public_storage_bucket, extract_public_index_path,\n    storage_base_path)', False, 'from Tests.private_build.upload_packs_private import download_and_extract_index, update_index_with_priced_packs, extract_packs_artifacts\n'), ((222, 64, 226, 113), 'Tests.private_build.upload_packs_private.update_index_with_priced_packs', 'update_index_with_priced_packs', ({(222, 95, 222, 117): 'private_storage_bucket', (223, 95, 223, 119): 'extract_destination_path', (224, 95, 224, 119): 'public_index_folder_path', (225, 95, 225, 107): 'changed_pack', (225, 109, 225, 113): 'True', (226, 95, 226, 112): 'storage_base_path'}, {}), '(private_storage_bucket,\n    extract_destination_path, public_index_folder_path, changed_pack, True,\n    storage_base_path)', False, 'from Tests.private_build.upload_packs_private import download_and_extract_index, update_index_with_priced_packs, extract_packs_artifacts\n'), ((26, 8, 26, 71), 'Tests.scripts.utils.logging_wrapper.exception', 'logging.exception', ({(26, 26, 26, 70): '"""Error in dummy index lock context manager."""'}, {}), "('Error in dummy index lock context manager.')", True, 'from Tests.scripts.utils import logging_wrapper as logging\n'), ((41, 68, 41, 104), 'os.scandir', 'os.scandir', ({(41, 79, 41, 103): 'public_index_folder_path'}, {}), '(public_index_folder_path)', False, 'import os\n'), ((49, 60, 49, 97), 'os.scandir', 'os.scandir', ({(49, 71, 49, 96): 'private_index_folder_path'}, {}), '(private_index_folder_path)', False, 'import os\n'), ((68, 14, 68, 66), 'os.path.join', 'os.path.join', ({(68, 27, 68, 51): 'public_index_folder_path', (68, 53, 68, 65): '"""index.json"""'}, {}), "(public_index_folder_path, 'index.json')", False, 'import os\n'), ((88, 8, 88, 90), 'Tests.scripts.utils.logging_wrapper.exception', 'logging.exception', ({(88, 26, 88, 89): '"""Failed in uploading index. Mismatch in index file generation."""'}, {}), "(\n    'Failed in uploading index. Mismatch in index file generation.')", True, 'from Tests.scripts.utils import logging_wrapper as logging\n'), ((89, 8, 89, 19), 'sys.exit', 'sys.exit', ({(89, 17, 89, 18): '(1)'}, {}), '(1)', False, 'import sys\n'), ((148, 12, 148, 95), 'Tests.scripts.utils.logging_wrapper.critical', 'logging.critical', ({(148, 29, 148, 94): '"""Error: Failed too long to acquire lock, exceeded max wait time."""'}, {}), "(\n    'Error: Failed too long to acquire lock, exceeded max wait time.')", True, 'from Tests.scripts.utils import logging_wrapper as logging\n'), ((149, 12, 149, 23), 'sys.exit', 'sys.exit', ({(149, 21, 149, 22): '(1)'}, {}), '(1)', False, 'import sys\n'), ((153, 12, 153, 52), 'Tests.scripts.utils.logging_wrapper.info', 'logging.info', ({(153, 25, 153, 51): '"""Waiting to acquire lock."""'}, {}), "('Waiting to acquire lock.')", True, 'from Tests.scripts.utils import logging_wrapper as logging\n'), ((174, 13, 174, 54), 'zipfile.ZipFile', 'ZipFile', ({(174, 21, 174, 48): 'downloaded_dummy_index_path', (174, 50, 174, 53): '"""r"""'}, {}), "(downloaded_dummy_index_path, 'r')", False, 'from zipfile import ZipFile\n'), ((73, 24, 73, 41), 'datetime.datetime.utcnow', 'datetime.utcnow', ({}, {}), '()', False, 'from datetime import datetime\n')]