# Copyright 2012 OpenStack Foundation
# All 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.

# Originally copied from python-glanceclient

import copy
import hashlib
import httplib
import json
import posixpath
import re
import socket
import StringIO
import struct
import urlparse


import OpenSSL
from oslo_log import log as logging
from six import moves
from tempest_lib import exceptions as lib_exc

from tempest import exceptions as exc

LOG = logging.getLogger(__name__)
USER_AGENT = 'tempest'
CHUNKSIZE = 1024 * 64  # 64kB
TOKEN_CHARS_RE = re.compile('^[-A-Za-z0-9+/=]*$')


class HTTPClient(object):

    def __init__(self, auth_provider, filters, **kwargs):
        self.auth_provider = auth_provider
        self.filters = filters
        self.endpoint = auth_provider.base_url(filters)
        endpoint_parts = urlparse.urlparse(self.endpoint)
        self.endpoint_scheme = endpoint_parts.scheme
        self.endpoint_hostname = endpoint_parts.hostname
        self.endpoint_port = endpoint_parts.port
        self.endpoint_path = endpoint_parts.path

        self.connection_class = self.get_connection_class(self.endpoint_scheme)
        self.connection_kwargs = self.get_connection_kwargs(
            self.endpoint_scheme, **kwargs)

    @staticmethod
    def get_connection_class(scheme):
        if scheme == 'https':
            return VerifiedHTTPSConnection
        else:
            return httplib.HTTPConnection

    @staticmethod
    def get_connection_kwargs(scheme, **kwargs):
        _kwargs = {'timeout': float(kwargs.get('timeout', 600))}

        if scheme == 'https':
            _kwargs['ca_certs'] = kwargs.get('ca_certs', None)
            _kwargs['cert_file'] = kwargs.get('cert_file', None)
            _kwargs['key_file'] = kwargs.get('key_file', None)
            _kwargs['insecure'] = kwargs.get('insecure', False)
            _kwargs['ssl_compression'] = kwargs.get('ssl_compression', True)

        return _kwargs

    def get_connection(self):
        _class = self.connection_class
        try:
            return _class(self.endpoint_hostname, self.endpoint_port,
                          **self.connection_kwargs)
        except httplib.InvalidURL:
            raise exc.EndpointNotFound

    def _http_request(self, url, method, **kwargs):
        """Send an http request with the specified characteristics.

        Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
        as setting headers and error handling.
        """
        # Copy the kwargs so we can reuse the original in case of redirects
        kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
        kwargs['headers'].setdefault('User-Agent', USER_AGENT)

        self._log_request(method, url, kwargs['headers'])

        conn = self.get_connection()

        try:
            url_parts = urlparse.urlparse(url)
            conn_url = posixpath.normpath(url_parts.path)
            LOG.debug('Actual Path: {path}'.format(path=conn_url))
            if kwargs['headers'].get('Transfer-Encoding') == 'chunked':
                conn.putrequest(method, conn_url)
                for header, value in kwargs['headers'].items():
                    conn.putheader(header, value)
                conn.endheaders()
                chunk = kwargs['body'].read(CHUNKSIZE)
                # Chunk it, baby...
                while chunk:
                    conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
                    chunk = kwargs['body'].read(CHUNKSIZE)
                conn.send('0\r\n\r\n')
            else:
                conn.request(method, conn_url, **kwargs)
            resp = conn.getresponse()
        except socket.gaierror as e:
            message = ("Error finding address for %(url)s: %(e)s" %
                       {'url': url, 'e': e})
            raise exc.EndpointNotFound(message)
        except (socket.error, socket.timeout) as e:
            message = ("Error communicating with %(endpoint)s %(e)s" %
                       {'endpoint': self.endpoint, 'e': e})
            raise exc.TimeoutException(message)

        body_iter = ResponseBodyIterator(resp)
        # Read body into string if it isn't obviously image data
        if resp.getheader('content-type', None) != 'application/octet-stream':
            body_str = ''.join([body_chunk for body_chunk in body_iter])
            body_iter = StringIO.StringIO(body_str)
            self._log_response(resp, None)
        else:
            self._log_response(resp, body_iter)

        return resp, body_iter

    def _log_request(self, method, url, headers):
        LOG.info('Request: ' + method + ' ' + url)
        if headers:
            headers_out = headers
            if 'X-Auth-Token' in headers and headers['X-Auth-Token']:
                token = headers['X-Auth-Token']
                if len(token) > 64 and TOKEN_CHARS_RE.match(token):
                    headers_out = headers.copy()
                    headers_out['X-Auth-Token'] = "<Token omitted>"
                LOG.info('Request Headers: ' + str(headers_out))

    def _log_response(self, resp, body):
        status = str(resp.status)
        LOG.info("Response Status: " + status)
        if resp.getheaders():
            LOG.info('Response Headers: ' + str(resp.getheaders()))
        if body:
            str_body = str(body)
            length = len(body)
            LOG.info('Response Body: ' + str_body[:2048])
            if length >= 2048:
                self.LOG.debug("Large body (%d) md5 summary: %s", length,
                               hashlib.md5(str_body).hexdigest())

    def json_request(self, method, url, **kwargs):
        kwargs.setdefault('headers', {})
        kwargs['headers'].setdefault('Content-Type', 'application/json')
        if kwargs['headers']['Content-Type'] != 'application/json':
            msg = "Only application/json content-type is supported."
            raise lib_exc.InvalidContentType(msg)

        if 'body' in kwargs:
            kwargs['body'] = json.dumps(kwargs['body'])

        resp, body_iter = self._http_request(url, method, **kwargs)

        if 'application/json' in resp.getheader('content-type', ''):
            body = ''.join([chunk for chunk in body_iter])
            try:
                body = json.loads(body)
            except ValueError:
                LOG.error('Could not decode response body as JSON')
        else:
            msg = "Only json/application content-type is supported."
            raise lib_exc.InvalidContentType(msg)