#!/usr/bin/env python3

import argparse
import json
import os
import requests
import requests.exceptions
import dotenv

dotenv.load_dotenv('config/config.env')
EXAMPLE_SIMULATIONS_FILENAME = os.path.join(os.path.dirname(__file__), 'example-projects.json')

EXAMPLE_SIMULATIONS_RUNS_FILENAME = os.path.join(os.path.dirname(__file__),
                                                 '..', 'apps', 'dispatch', 'src', 'app', 'components',
                                                 'simulations', 'browse', 'example-simulations.{}.json')

BIOSIMULATIONS_API_AUTH_ENDPOINT = 'https://auth.biosimulations.org/oauth/token'
BIOSIMULATIONS_API_AUDIENCE = 'dispatch.biosimulations.org'
BIOSIMULATIONS_API_CLIENT_ID = os.environ.get('CLIENT_ID', None)
BIOSIMULATIONS_API_CLIENT_SECRET = os.environ.get('CLIENT_SECRET', None)

if not BIOSIMULATIONS_API_CLIENT_ID:
    raise ValueError('A BioSimulations API client id (`CLIENT_ID`) must be provided')
if not BIOSIMULATIONS_API_CLIENT_SECRET:
    raise ValueError('A BioSimulations API client secret (`CLIENT_SECRET`) must be provided')


def get_status_endpoint(biosimulations_api):
    """ Get the endpoint for getting the status of runs

    Args:
        biosimulations_api (:obj:`str`): which deployment of the BioSimulations API to use (``dev``, ``org``, or ``local``)

    Returns:
        :obj:`str`: endpoint for getting the status of runs
    """
    if biosimulations_api == 'local':
        return 'http://localhost:3333/runs'

    return 'https://api.biosimulations.{}/runs'.format(biosimulations_api)


def get_publish_endpoint(biosimulations_api):
    """ Get the endpoint for publishing projects

    Args:
        biosimulations_api (:obj:`str`): which deployment of the BioSimulations API to use (``dev``, ``org``, or ``local``)

    Returns:
        :obj:`str`: endpoint for publishing projects
    """
    if biosimulations_api == 'local':
        return 'http://localhost:3333/projects'

    return 'https://api.biosimulations.{}/projects'.format(biosimulations_api)


def did_run_succeed(biosimulations_api, id):
    """ Check if a simulation run succeeded

    Args:
        biosimulations_api (:obj:`str`): which deployment of the BioSimulations API to use (``dev``, ``org``, or ``local``)
        id (:obj:`str`): id of simulation run

    Returns:
        :obj:`bool`: whether the simulation run succeeded
    """
    response = requests.get(get_status_endpoint(biosimulations_api) + '/' + id)
    response.raise_for_status()
    return response.json()['status'] == 'SUCCEEDED'


def get_auth_headers_for_biosimulations_api():
    """ Get authorization headers for using the BioSimulations REST API.

    Returns:
        :obj:`dict`: authorization headers
    """
    response = requests.post(BIOSIMULATIONS_API_AUTH_ENDPOINT, json={
        'client_id': BIOSIMULATIONS_API_CLIENT_ID,
        'client_secret': BIOSIMULATIONS_API_CLIENT_SECRET,
        'audience': BIOSIMULATIONS_API_AUDIENCE,
        "grant_type": "client_credentials",
    })
    response.raise_for_status()
    response_data = response.json()
    return {'Authorization': response_data['token_type'] + ' ' + response_data['access_token']}


def main(biosimulations_api, example_names, dry_run=False):
    """ Publish example project to BioSimulations

    Args:
        biosimulations_api (:obj:`str`): which deployment of the BioSimulations API to use (``dev``, ``org``, or ``local``)
        example_names (:obj:`list` of :obj:`str`): names of examples to publish. Default: publish all examples.
        dry_run (:obj:`bool`, optional): If :obj:`True`, do not submit simulations to runBioSimulations
    """

    # read examples
    with open(EXAMPLE_SIMULATIONS_FILENAME, 'r') as file:
        examples = json.load(file)
    num_total_examples = len(examples)

    # filter out examples that shouldn't be published
    examples = list(filter(
        lambda example:
        example['publishToBioSimulations'],
        examples))

    # filter to selected examples
    if example_names:
        examples = list(
            filter(lambda example: example['name'] in example_names, examples))

        missing_example_names = set(example_names).difference(
            set(example['name'] for example in examples))
        if missing_example_names:
            raise ValueError('No examples have the following names:\n  - {}'.format(
                '\n  - '.join(sorted(missing_example_names))))

    # get runs
    with open(EXAMPLE_SIMULATIONS_RUNS_FILENAME.format(biosimulations_api), 'r') as file:
        runs = {run['name']: run for run in json.load(file)}

    # filter out examples that don't have runs
    examples = list(filter(
        lambda example:
        example['name'] in runs,
        examples))

    # filter out examples whose runs didn't succeed
    examples = list(filter(
        lambda example:
        did_run_succeed(biosimulations_api, runs[example['name']]['id']),
        examples))

    # publish examples
    auth_headers = get_auth_headers_for_biosimulations_api()
    created = []
    updated = []
    for example in examples:
        run = runs[example['name']]

        if not example['bioSimulationsId']:
            raise ValueError('{} must provide a BioSimulations id'.format(example['name']))

        response = requests.get(get_publish_endpoint(biosimulations_api) + '/' + example['bioSimulationsId'])
        try:
            response.raise_for_status()
            if run['id'] == response.json()['simulationRun']:
                continue

            method = requests.put
            endpoint = get_publish_endpoint(biosimulations_api) + '/' + example['bioSimulationsId']
            updated.append(example['name'])
        except requests.exceptions.RequestException:
            method = requests.post
            endpoint = get_publish_endpoint(biosimulations_api)
            created.append(example['name'])

        if not dry_run:
            response = method(
                endpoint,
                headers=auth_headers,
                json={
                    "id": example['bioSimulationsId'],
                    "simulationRun": run['id'],
                })
            response.raise_for_status()

    print('{} examples were created{}'.format(len(created), ''.join('\n  ' + example for example in created)))
    print('{} examples were updated{}'.format(len(updated), ''.join('\n  ' + example for example in updated)))
    print('{} examples were already published'.format(len(examples) - (len(created) + len(updated))))
    print('{} examples were skipped'.format(num_total_examples - len(examples)))


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Publish the example simulation runs to BioSimulations.'
    )
    parser.add_argument(
        '--biosimulations-api', type=str, default='dev',
        help='BioSimulations API which projects should be published to (`dev`, `org`, `local`). Default: `dev`.'
    )
    parser.add_argument(
        '--example', type=str, nargs='*',
        help='Names of the example projects to publish. Default: publish all projects.',
        default=None,
        dest='example_names',
    )
    parser.add_argument(
        '--dry-run',
        help='If set, do not publish simulations to BioSimulations',
        action='store_true',

    )
    args = parser.parse_args()

    main(biosimulations_api=args.biosimulations_api,
         example_names=args.example_names,
         dry_run=args.dry_run)
