# Copyright 2018 AT&T Intellectual Property.  All other rights reserved.
#
# 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.

import json
import logging
import uuid

from shipyard_client.api_client.shipyard_api_client import ShipyardClient
from shipyard_client.api_client.shipyardclient_context import \
    ShipyardClientContext
import yaml

from pegleg.engine import exceptions
from pegleg.engine import site
from pegleg.engine.util import files
from pegleg.engine.util.files import add_representer_ordered_dict
from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement

LOG = logging.getLogger(__name__)


class AuthValuesError(exceptions.PeglegBaseException):
    """Shipyard authentication failed. """
    def __init__(self, *, diagnostic):
        self.diagnostic = diagnostic


class DocumentUploadError(exceptions.PeglegBaseException):
    """Exception occurs while uploading documents"""
    def __init__(self, message):
        self.message = message


class ShipyardHelper(object):
    """
    A helper class for Shipyard. It performs the following operation:
    1. Validates the authentication parameters required for Keystone
    2. Uploads the document to Shipyard buffer
    3. Commits the document
    4. Formats response from Shipyard api_client
    """
    def __init__(self, context, buffer_mode='replace'):
        """
        Initializes params to be used by Shipyard

        :param context: ShipyardHelper context object that contains
                        params for initializing ShipyardClient with
                        correct client context and the site_name.
        """
        self.ctx = context
        self.api_parameters = self.ctx.obj['API_PARAMETERS']
        self.auth_vars = self.api_parameters.get('auth_vars')
        self.context_marker = self.ctx.obj['context_marker']
        if self.context_marker is None:
            self.context_marker = str(uuid.uuid4())
            LOG.debug("context_marker is %s", self.context_marker)
        self.site_name = self.ctx.obj['site_name']
        self.client_context = ShipyardClientContext(
            self.auth_vars, self.context_marker)
        self.api_client = ShipyardClient(self.client_context)
        self.buffer_mode = buffer_mode
        self.collection = self.ctx.obj.get('collection', self.site_name)

    def upload_documents(self):
        """Uploads documents to Shipyard"""

        collected_documents = files.collect_files_by_repo(self.site_name)

        collection_data = [site.get_deployment_data_doc(self.site_name)]
        LOG.info("Processing %d collection(s)", len(collected_documents))
        for idx, document in enumerate(collected_documents):
            # Decrypt the documents if encrypted
            pegleg_secret_mgmt = PeglegSecretManagement(
                docs=collected_documents[document])
            decrypted_documents = pegleg_secret_mgmt.get_decrypted_secrets()
            collection_data.extend(decrypted_documents)
        add_representer_ordered_dict()
        collection_as_yaml = yaml.dump_all(
            collection_data, Dumper=yaml.SafeDumper)

        # Append flag is not required for the first
        # collection being uploaded to Shipyard. It
        # is needed for subsequent collections.
        if self.buffer_mode in ['append', 'replace']:
            buffer_mode = self.buffer_mode
        else:
            raise exceptions.InvalidBufferModeException()

        try:
            self.validate_auth_vars()
            resp_text = self.api_client.post_configdocs(
                collection_id=self.collection,
                buffer_mode=buffer_mode,
                document_data=collection_as_yaml)

        except AuthValuesError as ave:
            resp_text = "Error: {}".format(ave.diagnostic)
            raise DocumentUploadError(resp_text)
        except Exception as ex:
            resp_text = (
                "Error: Unable to invoke action due to: {}".format(str(ex)))
            LOG.debug(resp_text, exc_info=True)
            raise DocumentUploadError(resp_text)

        # FIXME: Standardize status_code in Deckhand to avoid this
        # workaround.
        code = 0
        if hasattr(resp_text, 'status_code'):
            code = resp_text.status_code
        elif hasattr(resp_text, 'code'):
            code = resp_text.code
        if code >= 400:
            if hasattr(resp_text, 'content'):
                raise DocumentUploadError(resp_text.content)
            else:
                raise DocumentUploadError(resp_text)
        else:
            output = self.formatted_response_handler(resp_text)
            LOG.info("Uploaded document in buffer %s ", output)

        # Commit in the last iteration of the loop when all the documents
        # have been pushed to Shipyard buffer.
        return self.commit_documents()

    def commit_documents(self):
        """Commit Shipyard buffer documents """

        LOG.info("Commiting Shipyard buffer documents")

        try:
            resp_text = self.formatted_response_handler(
                self.api_client.commit_configdocs())
        except Exception as ex:
            resp_text = (
                "Error: Unable to invoke action due to: {}".format(str(ex)))
            raise DocumentUploadError(resp_text)
        return resp_text

    def validate_auth_vars(self):
        """Checks that the required authorization varible have been entered"""
        required_auth_vars = ['auth_url']
        err_txt = []
        for var in required_auth_vars:
            if self.auth_vars[var] is None:
                err_txt.append(
                    'Missing the required authorization variable: '
                    '--os-{}'.format(var.replace('_', '-')))
        if err_txt:
            for var in self.auth_vars:
                if (self.auth_vars.get(var) is None
                        and var not in required_auth_vars):
                    err_txt.append(
                        '- Also not set: --os-{}'.format(
                            var.replace('_', '-')))
            raise AuthValuesError(diagnostic='\n'.join(err_txt))

    def formatted_response_handler(self, response):
        """Base format handler for either json or yaml depending on call"""
        call = response.headers['Content-Type']
        if 'json' in call:
            try:
                return json.dumps(response.json(), indent=4)
            except ValueError:
                return (
                    "This is not json and could not be printed as such. \n"
                    + response.text)
