#!/usr/bin/env python
# pylint: disable=missing-docstring
# Copyright 2018 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from __future__ import print_function  # noqa: F401
# import httplib
import json
import os
import time
import requests

from ansible.module_utils.basic import AnsibleModule


class AzurePublisherException(Exception):
    '''Exception class for AzurePublisher'''
    pass


class AzurePublisher(object):
    '''Python class to represent the Azure Publishing portal https://cloudpartner.azure.com'''

    # pylint: disable=too-many-arguments
    def __init__(self,
                 publisher_id,
                 client_info,
                 ssl_verify=True,
                 api_version='2017-10-31',
                 debug=False):
        '''
          :publisher_id: string of the publisher id
          :client_info: a dict containing the client_id, client_secret to get an access_token
        '''
        self._azure_server = 'https://cloudpartner.azure.com/api/publishers/{}'.format(publisher_id)
        self.client_info = client_info
        self.ssl_verify = ssl_verify
        self.api_version = 'api-version={}'.format(api_version)
        self.debug = debug
        # if self.debug:
        # import httplib
        # httplib.HTTPSConnection.debuglevel = 1
        # httplib.HTTPConnection.debuglevel = 1

        self._access_token = None

    @property
    def server(self):
        '''property for  server url'''
        return self._azure_server

    @property
    def token(self):
        '''property for the access_token
            curl --data-urlencode "client_id=$AZURE_CLIENT_ID" \
                --data-urlencode "client_secret=$AZURE_CLIENT_SECRET" \
                --data-urlencode "grant_type=client_credentials" \
                --data-urlencode "resource=https://cloudpartner.azure.com" \
                https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/token
        '''
        if self._access_token is None:
            url = 'https://login.microsoftonline.com/{}/oauth2/token'.format(self.client_info['tenant_id'])
            data = {
                'client_id': {self.client_info['client_id']},
                'client_secret': self.client_info['client_secret'],
                'grant_type': 'client_credentials',
                'resource': 'https://cloudpartner.azure.com'
            }

            results = AzurePublisher.request('POST', url, data, {})
            jres = results.json()
            self._access_token = jres['access_token']

        return self._access_token

    def get_offers(self, offer=None, version=None, slot='preview'):
        ''' fetch all offers by publisherid '''
        url = '/offers'

        if offer is not None:
            url += '/{}'.format(offer)
            if version is not None:
                url += '/versions/{}'.format(version)
            if slot == 'preview':
                url += '/slot/{}'.format(slot)

        url += '?{}'.format(self.api_version)

        return self.prepare_action(url)

    def get_operations(self, offer, operation=None, status=None):
        ''' create or modify an offer '''
        url = '/offers/{0}/submissions'.format(offer)

        if operation is not None:
            url += '/operations/{0}'.format(operation)

        if not url.endswith('/'):
            url += '/'

        url += '?{0}'.format(self.api_version)

        if status is not None:
            url += '&status={0}'.format(status)

        return self.prepare_action(url, 'GET')

    def cancel_operation(self, offer):
        ''' create or modify an offer '''
        url = '/offers/{0}/cancel?{1}'.format(offer, self.api_version)

        return self.prepare_action(url, 'POST')

    def publish(self, offer, emails):
        ''' publish an offer '''
        url = '/offers/{0}/publish?{1}'.format(offer, self.api_version)

        data = {
            'metadata': {
                'notification-emails': ','.join(emails),
            }
        }

        return self.prepare_action(url, 'POST', data=data)

    def go_live(self, offer):
        ''' create or modify an offer '''
        url = '/offers/{0}/golive?{1}'.format(offer, self.api_version)

        return self.prepare_action(url, 'POST')

    def create_or_modify_offer(self, offer, data=None, modify=False):
        ''' create or modify an offer '''
        url = '/offers/{0}?{1}'.format(offer, self.api_version)

        headers = None

        if modify:
            headers = {
                'If-Match': '*',
            }

        return self.prepare_action(url, 'PUT', data=data, add_headers=headers)

    def prepare_action(self, url, action='GET', data=None, add_headers=None):
        '''perform the http request

           :action: string of either GET|POST
        '''
        headers = {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'Authorization': 'Bearer {}'.format(self.token)
        }

        if add_headers is not None:
            headers.update(add_headers)

        if data is None:
            data = ''
        else:
            data = json.dumps(data)

        return AzurePublisher.request(action.upper(), self.server + url, data, headers)

    def cancel_and_wait_for_operation(self, params):
        '''cancel the current publish operation and wait for operation to complete'''

        # cancel the publish operation
        self.cancel_operation(offer=params['offer'])

        # we need to wait here for 'submissionState' to move to 'canceled'
        while True:
            # fetch operations
            ops = self.get_operations(params['offer'])
            if self.debug:
                print(ops.json())
            if ops.json()[0]['submissionState'] == 'canceled':
                break

            time.sleep(5)

        return ops

    def manage_offer(self, params):
        ''' handle creating or modifying offers'''
        # fetch the offer to verify it exists:
        results = self.get_offers(offer=params['offer'])

        if results.status_code == 200 and params['force']:
            return self.create_or_modify_offer(offer=params['offer'], data=params['offer_data'], modify=True)

        return self.create_or_modify_offer(offer=params['offer'], data=params['offer_data'])

    @staticmethod
    def request(action, url, data=None, headers=None, ssl_verify=True):
        req = requests.Request(action.upper(), url, data=data, headers=headers)

        session = requests.Session()
        req_prep = session.prepare_request(req)
        response = session.send(req_prep, verify=ssl_verify)

        return response

    @staticmethod
    def run_ansible(params):
        '''perform the ansible operations'''
        client_info = {
            'tenant_id': params['tenant_id'],
            'client_id': params['client_id'],
            'client_secret': params['client_secret']}

        apc = AzurePublisher(params['publisher'],
                             client_info,
                             debug=params['debug'])

        if params['state'] == 'offer':
            results = apc.manage_offer(params)
        elif params['state'] == 'publish':
            results = apc.publish(offer=params['offer'], emails=params['emails'])
            results.json = lambda: ''
        elif params['state'] == 'cancel_op':
            results = apc.cancel_and_wait_for_operation(params)
        elif params['state'] == 'go_live':
            results = apc.go_live(offer=params['offer'])
        else:
            raise AzurePublisherException('Unsupported query type: {}'.format(params['state']))

        changed = False

        if results.status_code in [200, 201, 202]:
            changed = True

        return {'data': results.json(), 'changed': changed, 'status_code': results.status_code}


# pylint: disable=too-many-branches
def main():
    ''' ansible oc module for secrets '''

    module = AnsibleModule(
        argument_spec=dict(
            state=dict(default='offer', choices=['offer', 'cancel_op', 'go_live', 'publish']),
            force=dict(default=False, type='bool'),
            publisher=dict(default='redhat', type='str'),
            debug=dict(default=False, type='bool'),
            tenant_id=dict(default=os.environ.get('AZURE_TENANT_ID'), type='str'),
            client_id=dict(default=os.environ.get('AZURE_CLIENT_ID'), type='str'),
            client_secret=dict(default=os.environ.get('AZURE_CLIENT_SECRET'), type='str'),
            offer=dict(default=None, type='str'),
            offer_data=dict(default=None, type='dict'),
            emails=dict(default=None, type='list'),
        ),

        required_if=[
            ["state", "offer", ["offer_data"]],
        ],
    )

    # Verify we recieved either a valid key or edits with valid keys when receiving a src file.
    # A valid key being not None or not ''.
    if (module.params['tenant_id'] is None or module.params['client_id'] is None or
            module.params['client_secret'] is None):
        return module.fail_json(**{'failed': True,
                                   'msg': 'Please specify tenant_id, client_id, and client_secret'})

    rval = AzurePublisher.run_ansible(module.params)

    if int(rval['status_code']) >= 300:
        rval['msg'] = 'Failed. status_code {}'.format(rval['status_code'])
        return module.fail_json(**rval)

    return module.exit_json(**rval)


if __name__ == '__main__':
    main()
