import mimetypes
import os
import sys
import time
from zipfile import ZipFile

import click
import dateutil.parser
import requests
# we have to do this right now because of how our S3 buckets are named. This should be removed once we've fixed that
import urllib3
from dotenv import dotenv_values
from requests_toolbelt.multipart.encoder import MultipartEncoder

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

headers = {'Content-type': 'application/json', 'Accept': 'application/json'}


class CommonCommands(click.core.Command):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.params.insert(0, click.Option(('--token',), required=True, help='Your Testery API token.'))
        self.params.insert(0, click.Option(('--testery-dev',), is_flag=True, default=False, hidden=True))


def get_api_url(is_dev):
    if is_dev:
        return 'https://api.dev.testery.io/api'
    return 'https://api.testery.io/api'


def report_test_run(test_run, output):
    if output == "teamcity":
        report_teamcity_test_run(test_run)
    elif output == "json":
        print(test_run)
    else:
        report_pretty_test_run(test_run)


def report_pretty_test_run(test_run):
    if test_run['status'] == 'SUBMITTED':
        print("Waiting for test run to start")
    elif test_run['totalCount'] == 0 and test_run['status'] == 'RUNNING':
        print("Getting list of tests to run")
    elif test_run['totalCount'] == 0:
        print("No tests run")
    elif test_run['ignoredCount'] == 0:
        if test_run['status'] == 'RUNNING':
            text = "Running"
        else:
            text = "Completed"
        print("%s: %s of %s pass with %s fail" % (text, test_run['passCount'], test_run['totalCount'], test_run['failCount']))
    else:
        if test_run['status'] == 'RUNNING':
            text = "Running"
        else:
            text = "Completed"
        print("%s: %s of %s pass with %s fail and %s ignored" % (text, test_run['passCount'], test_run['totalCount'], test_run['failCount'], test_run['ignoredCount']))


def report_teamcity_test_run(test_run):
    if test_run['status'] == 'FAIL':
        print("##teamcity[buildProblem description='%s: %s passing, %s failing out of %s total']" % (
            test_run['status'], test_run['passCount'], test_run['failCount'], test_run['totalCount']))
    else:
        print("##teamcity[buildStatus text='%s: %s passing, %s failing out of %s total']" % (
            test_run['status'], test_run['passCount'], test_run['failCount'], test_run['totalCount']))


def api_wait_test_run(api_url, token, test_run_id, output, fail_on_failure):
    headers['Authorization'] = "Bearer " + token

    test_run = requests.get(api_url + '/test-runs/' + str(test_run_id), headers=headers).json()

    report_test_run(test_run, output)

    while test_run['status'] not in ['PASS', 'FAIL', 'CANCELED']:
        time.sleep(15)
        test_run = requests.get(api_url + '/test-runs/' + str(test_run_id), headers=headers).json()
        report_test_run(test_run, output)

    if (test_run['status'] == 'FAIL' or test_run['status'] == 'CANCELED') and fail_on_failure:
        print('Test run had failures')
        sys.exit(1)


def api_wait_deploy(api_url, token, deploy_id, output, fail_on_failure):
    headers['Authorization'] = "Bearer " + token

    test_runs = requests.get(api_url + '/test-runs/by-deploy/' + str(deploy_id), headers=headers).json()

    for test_run in test_runs:
        click.echo("Waiting for test run: %s" % test_run['id'])
        api_wait_test_run(api_url, token, test_run['id'], output, fail_on_failure)

    click.echo("All test runs for deployment #%s are complete." % deploy_id)


def add_vars_from_file(file: str, body: dict, overwrite: bool = False):
    new_vars = {}
    json = []
    for var in body["variables"]:
        new_vars[var['key']] = var
    config = dotenv_values(file)
    for var in config:
        if var in new_vars:
            if overwrite:
                new_vars[var] = {'key': var, 'value': config[var], 'encrypted': False, 'file': False}
        else:
            new_vars[var] = {'key': var, 'value': config[var], 'encrypted': False, 'file': False}
    for value in new_vars.values():
        json.append(value)
    body["variables"] = json


def find_environment_by_key(api_url, token, environment_key):
    environments_response = requests.get(api_url + '/environments', headers=headers)

    environments = environments_response.json()

    for environment in environments:
        if environment['key'] == environment_key:
            return environment

    raise Exception("Environment with key not found: " + environment_key)


def find_test_plan_by_key(api_url, token, test_plan_key):
    test_plans_response = requests.get(api_url + '/test-plans', headers=headers)

    testplans = test_plans_response.json()

    for test_plan in testplans:
        if test_plan['key'] == test_plan_key:
            return test_plan

    raise Exception("Test Plan with key not found: " + test_plan_key)


def find_pipeline_stage_by_name(api_url, pipeline_stage_name):
    stages_response = requests.get(api_url + '/pipeline-stages', headers=headers)
    stages_response.raise_for_status()

    stages = stages_response.json()
    lower_name = pipeline_stage_name.lower()
    for stage in stages:
        if stage['name'].lower() == lower_name:
            return stage

    stage_names = map(lambda s: s['name'], stages)
    message = "Could not find pipeline stage with name '" + pipeline_stage_name + "'. Known names: " + ', '.join(stage_names)
    raise Exception(message)


def find_project_by_key(api_url, token, project_key):
    response = requests.get(api_url + '/projects', headers=headers)

    projects = response.json()

    for project in projects:
        if project['key'] == project_key:
            return project

    raise Exception("Project with key not found: " + project_key)


def find_test_suite_by_name(api_url, suite_name, project_id):
    test_suites_response = requests.get(api_url + f'/projects/{project_id}/test-suites', headers=headers)

    test_suites = test_suites_response.json()
    lower_name = suite_name.lower()

    for suite in test_suites:
        if suite['name'].lower() == lower_name:
            return suite

    test_suite_names = map(lambda s: s['name'], test_suites)
    message = "Could not find test suite with name '" + suite_name + "'. Known names: " + ', '.join(test_suite_names)
    raise Exception(message)


def handle_variables(variables, request):
    if variables:
        vars = []
        for var in variables:
            (vkey, vval) = var.split("=")

            if vkey.startswith("secure:"):
                vkey = vkey.replace("secure:", "")
                encrypted = True
            else:
                encrypted = False

            v = {"key": vkey, "value": vval, "encrypted": encrypted}
            vars.append(v)
        request["variables"] = vars


def create_zip_file(path, temp_dir):
    zip_file = ZipFile(os.path.join(temp_dir, str(time.time()) + '.zip'), 'w')
    len_path = len(path)
    with zip_file as zip_file:
        exclude = {'.git', 'node_modules'}
        for root, dirs, files in os.walk(path):
            dirs[:] = [d for d in dirs if d not in exclude]
            for file in files:
                file_path = os.path.join(root, file)
                zip_file.write(file_path, file_path[len_path:])
    return zip_file


def upload_build_artifact_dir(base_api_url, file_path, dir_path):
    for file in os.listdir(file_path):
        new_file_path = os.path.join(file_path, file)
        if os.path.isdir(new_file_path):
            dir_name = os.path.basename(new_file_path)
            upload_build_artifact_dir(
                base_api_url, new_file_path, dir_path + dir_name + '/')
        else:
            upload_build_artifact_file(base_api_url, new_file_path, dir_path)


def upload_build_artifact_file(base_api_url, file_path, dir_path):
    file = dir_path + os.path.basename(file_path)

    upload_result = requests.get(base_api_url + file, headers=headers)

    upload_url = upload_result.text

    if upload_result.status_code != 200 or upload_url == "invalid request":
        raise Exception("Could not upload artifacts. Make sure you specified the correct project key.")

    result = upload_file(
        upload_url, {'Accept': 'application/json'}, file_path, False)

    if result.status_code != 200:
        raise Exception("Got status code " + str(result.status_code) +
                        " when trying to upload file with path " + file_path)


def upload_file(url, headers, file_path, for_add_file):
    file_name = os.path.basename(file_path)
    mime_type = mimetypes.guess_type(file_path)[0]

    if mime_type is None:
        mime_type = "text/plain"

    if for_add_file:
        data = MultipartEncoder(
            fields={'file': (file_name, open(file_path, 'rb'), mime_type)})
        headers['Content-Type'] = data.content_type
        return requests.post(url, headers=headers, data=data)
    else:
        headers['Content-Type'] = mime_type
        data = open(file_path, 'rb')
        return requests.put(url, data=data, verify=False)


def wait_for_maintenance_window():
    status = requests.get('http://status.testery.io/api/v2/scheduled-maintenances/active.json', headers=headers)
    status.raise_for_status()
    scheduled_maintenances = status.json().get('scheduled_maintenances')

    if len(scheduled_maintenances) > 0:
        scheduled_until = dateutil.parser.parse(scheduled_maintenances[0]['scheduled_until'])
        print("Testery is undergoing scheduled maintenance which is scheduled to end at %s UTC. To make sure you have a stable test run, we are pausing your build. Your build will resume automatically as soon as the system is confirmed to be fully operational. Learn more about the event at http://status.testery.io/\n" % scheduled_until)

        while len(scheduled_maintenances) > 0:
            print(".")
            time.sleep(300)
            status = requests.get('http://status.testery.io/api/v2/scheduled-maintenances/active.json', headers=headers)
            status.raise_for_status()
            scheduled_maintenances = status.json().get('scheduled_maintenances')

        print("Testery's scheduled maintenance is now complete. We'd like to thank you for your patience and for being such an awesome customer. Happy testing and good luck with this test run!")
