import logging

import six
from lxml import etree

from zeep import ns
from zeep.exceptions import Fault
from zeep.utils import qname_attr
from zeep.wsdl import messages
from zeep.wsdl.definitions import Binding, Operation

logger = logging.getLogger(__name__)

NSMAP = {
    'http': ns.HTTP,
    'wsdl': ns.WSDL,
    'mime': ns.MIME,
}


class HttpBinding(Binding):

    def create_message(self, operation, *args, **kwargs):
        if isinstance(operation, six.string_types):
            operation = self.get(operation)
            if not operation:
                raise ValueError("Operation not found")
        return operation.create(*args, **kwargs)

    def process_service_port(self, xmlelement, force_https=False):
        address_node = xmlelement.find('http:address', namespaces=NSMAP)
        if address_node is None:
            raise ValueError("No `http:address` node found")

        # Force the usage of HTTPS when the force_https boolean is true
        location = address_node.get('location')
        if force_https and location and location.startswith('http://'):
            logger.warning("Forcing http:address location to HTTPS")
            location = 'https://' + location[8:]

        return {
            'address': location
        }

    @classmethod
    def parse(cls, definitions, xmlelement):
        name = qname_attr(xmlelement, 'name', definitions.target_namespace)
        port_name = qname_attr(xmlelement, 'type', definitions.target_namespace)

        obj = cls(definitions.wsdl, name, port_name)
        for node in xmlelement.findall('wsdl:operation', namespaces=NSMAP):
            operation = HttpOperation.parse(definitions, node, obj)
            obj._operation_add(operation)
        return obj

    def process_reply(self, client, operation, response):
        if response.status_code != 200:
            return self.process_error(response.content)
            raise NotImplementedError("No error handling yet!")
        return operation.process_reply(response.content)

    def process_error(self, doc):
        raise Fault(message=doc)


class HttpPostBinding(HttpBinding):

    def send(self, client, options, operation, args, kwargs):
        """Called from the service"""
        operation_obj = self.get(operation)
        if not operation_obj:
            raise ValueError("Operation %r not found" % operation)

        serialized = operation_obj.create(*args, **kwargs)

        url = options['address'] + serialized.path
        response = client.transport.post(
            url, serialized.content, headers=serialized.headers)
        return self.process_reply(client, operation_obj, response)

    @classmethod
    def match(cls, node):
        """Check if this binding instance should be used to parse the given
        node.

        :param node: The node to match against
        :type node: lxml.etree._Element

        """
        http_node = node.find(etree.QName(NSMAP['http'], 'binding'))
        return http_node is not None and http_node.get('verb') == 'POST'


class HttpGetBinding(HttpBinding):

    def send(self, client, options, operation, args, kwargs):
        """Called from the service"""
        operation_obj = self.get(operation)
        if not operation_obj:
            raise ValueError("Operation %r not found" % operation)

        serialized = operation_obj.create(*args, **kwargs)

        url = options['address'] + serialized.path
        response = client.transport.get(
            url, serialized.content, headers=serialized.headers)
        return self.process_reply(client, operation_obj, response)

    @classmethod
    def match(cls, node):
        """Check if this binding instance should be used to parse the given
        node.

        :param node: The node to match against
        :type node: lxml.etree._Element

        """
        http_node = node.find(etree.QName(ns.HTTP, 'binding'))
        return http_node is not None and http_node.get('verb') == 'GET'


class HttpOperation(Operation):
    def __init__(self, name, binding, location):
        super(HttpOperation, self).__init__(name, binding)
        self.location = location

    def process_reply(self, envelope):
        return self.output.deserialize(envelope)

    @classmethod
    def parse(cls, definitions, xmlelement, binding):
        """

            <wsdl:operation name="GetLastTradePrice">
              <http:operation location="GetLastTradePrice"/>
              <wsdl:input>
                <mime:content type="application/x-www-form-urlencoded"/>
              </wsdl:input>
              <wsdl:output>
                <mime:mimeXml/>
              </wsdl:output>
            </wsdl:operation>

        """
        name = xmlelement.get('name')

        http_operation = xmlelement.find('http:operation', namespaces=NSMAP)
        location = http_operation.get('location')
        obj = cls(name, binding, location)

        for node in xmlelement:
            tag_name = etree.QName(node.tag).localname
            if tag_name not in ('input', 'output'):
                continue

            # XXX Multiple mime types may be declared as alternatives
            message_node = None
            nodes = list(node)
            if len(nodes) > 0:
                message_node = nodes[0]
            message_class = None
            if message_node is not None:
                if message_node.tag == etree.QName(ns.HTTP, 'urlEncoded'):
                    message_class = messages.UrlEncoded
                elif message_node.tag == etree.QName(ns.HTTP, 'urlReplacement'):
                    message_class = messages.UrlReplacement
                elif message_node.tag == etree.QName(ns.MIME, 'content'):
                    message_class = messages.MimeContent
                elif message_node.tag == etree.QName(ns.MIME, 'mimeXml'):
                    message_class = messages.MimeXML

            if message_class:
                msg = message_class.parse(definitions, node, obj)
                assert msg
                setattr(obj, tag_name, msg)
        return obj

    def resolve(self, definitions):
        super(HttpOperation, self).resolve(definitions)
        if self.output:
            self.output.resolve(definitions, self.abstract.output_message)
        if self.input:
            self.input.resolve(definitions, self.abstract.input_message)
