import unittest

from lxml import etree

from soapfish import wsa, xsd
from soapfish.core import SOAPError, SOAPRequest, SOAPResponse
from soapfish.middlewares import ExceptionToSoapFault
from soapfish.soap_dispatch import SOAPDispatcher
from soapfish.testutil import EchoInputHeader, EchoOutputHeader, echo_handler, echo_service


class SOAPDispatcherTest(unittest.TestCase):
    def test_can_dispatch_good_soap_message(self):
        handler, handler_state = echo_handler()
        dispatcher = SOAPDispatcher(echo_service(handler))
        soap_message = (
            '<ns1:echoRequest xmlns:ns1="http://soap.example/echo/types">'
            '<value>foobar</value>'
            '</ns1:echoRequest>'
        )
        request_message = self._wrap_with_soap_envelope(soap_message)
        request = SOAPRequest({'SOAPACTION': 'echo', 'REQUEST_METHOD': 'POST'}, request_message)

        response = dispatcher.dispatch(request)
        self.assert_is_successful_response(response, handler_state)
        self.assertEqual('foobar', handler_state.input_.value)

        response_document = etree.fromstring(response.http_content)
        response_xml = etree.tostring(response_document, pretty_print=True)
        expected_xml = (
            b'<ns0:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/">\n'
            b'  <ns0:Body>\n'
            b'    <ns0:echoResponse xmlns:ns0="http://soap.example/echo/types">\n'
            b'      <value>foobar</value>\n'
            b'    </ns0:echoResponse>\n'
            b'  </ns0:Body>\n'
            b'</ns0:Envelope>\n'
        )
        self.assertEqual(expected_xml, response_xml)

    def test_can_validate_soap_message(self):
        handler, handler_state = echo_handler()
        dispatcher = SOAPDispatcher(echo_service(handler))
        soap_message = (
            '<ns1:echoRequest xmlns:ns1="http://soap.example/echo/types">'
            '<invalid>foobar</invalid>'
            '</ns1:echoRequest>'
        )
        request_message = self._wrap_with_soap_envelope(soap_message)
        request = SOAPRequest({'SOAPACTION': 'echo', 'REQUEST_METHOD': 'POST'}, request_message)
        response = dispatcher.dispatch(request)
        self.assertFalse(handler_state.was_called)
        self.assert_is_soap_fault(response, partial_fault_string="Element 'invalid': This element is not expected. "
                                  'Expected is ( value ).')

    def test_can_reject_malformed_xml_soap_message(self):
        request = SOAPRequest({'SOAPACTION': 'echo', 'REQUEST_METHOD': 'POST'}, 'garbage')
        dispatcher = SOAPDispatcher(echo_service())
        response = dispatcher.dispatch(request)
        self.assertEqual(500, response.http_status_code)
        self.assertEqual('text/xml', response.http_headers['Content-Type'])
        self.assert_is_soap_fault(response, partial_fault_string="Start tag expected, '<' not found")

    def test_can_include_imported_schemas_during_validation(self):
        # In case the SOAPDispatcher would not use imported schemas for
        # validation it would fail because the 'code' tag is only defined in
        # the imported schema
        handler, handler_state = echo_handler()
        service = echo_service(handler)

        class CodeType(xsd.String):
            pattern = r'[0-9]{5}'

        class Container(xsd.ComplexType):
            value = xsd.Element(CodeType)
        code_schema = xsd.Schema('http://soap.example/included',
                                 location='http://soap.example/included',
                                 elementFormDefault=xsd.ElementFormDefault.UNQUALIFIED,
                                 simpleTypes=[CodeType],
                                 complexTypes=[Container],
                                 elements={'foo': xsd.Element(Container)},
                                 )
        service.methods[0].input = 'foo'
        service.schemas[0].imports = [code_schema]
        # The setup is a bit simplistic because the <code> tag is not parsed
        # into a soapfish model element for the handler but this was enough
        # to trigger the bug
        dispatcher = SOAPDispatcher(service)
        wsgi_environ = {'SOAPACTION': 'echo', 'REQUEST_METHOD': 'POST'}
        soap_message = '<ns0:foo xmlns:ns0="http://soap.example/included"><value>12345</value></ns0:foo>'
        request = SOAPRequest(wsgi_environ, self._wrap_with_soap_envelope(soap_message))
        response = dispatcher.dispatch(request)
        self.assert_is_successful_response(response, handler_state)
        self.assertEqual('12345', handler_state.input_.value)

    def test_can_reject_non_soap_xml_body(self):
        request = SOAPRequest({'SOAPACTION': 'echo', 'REQUEST_METHOD': 'POST'}, '<some>xml</some>')
        dispatcher = SOAPDispatcher(echo_service())