from lxml import etree

from tests.utils import assert_nodes_equal, load_xml, render_node
from zeep import xsd
from zeep.xsd import builtins
from zeep.xsd.context import ParserContext
from zeep.xsd.schema import Schema


def parse_schema_node(node):
    parser_context = ParserContext()
    schema = Schema(
        node=node,
        transport=None,
        location=None,
        parser_context=parser_context)
    return schema


def test_schema_empty():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
              targetNamespace="http://tests.python-zeep.org/"
              elementFormDefault="qualified"
              attributeFormDefault="unqualified">
        </schema>
    """)
    schema = parse_schema_node(node)
    root = list(schema._schemas.values())[0]
    assert root._element_form == 'qualified'
    assert root._attribute_form == 'unqualified'


def test_element_simle_types():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
              targetNamespace="http://tests.python-zeep.org/">
            <element name="foo" type="string" />
            <element name="bar" type="int" />
        </schema>
    """)
    schema = parse_schema_node(node)
    assert schema.get_element('{http://tests.python-zeep.org/}foo')
    assert schema.get_element('{http://tests.python-zeep.org/}bar')


def test_element_simple_type_annotation():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
              targetNamespace="http://tests.python-zeep.org/">
            <element name="foo" type="string">
                <annotation>
                    <documentation>HOI!</documentation>
                </annotation>
            </element>
        </schema>
    """)
    schema = parse_schema_node(node)
    element = schema.get_element('{http://tests.python-zeep.org/}foo')
    assert element


def test_element_default_type():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:tns="http://tests.python-zeep.org/"
                targetNamespace="http://tests.python-zeep.org/">
            <element name="foo" />
        </schema>
    """)
    schema = parse_schema_node(node)
    element = schema.get_element('{http://tests.python-zeep.org/}foo')
    assert isinstance(element.type, builtins.AnyType)


def test_element_simple_type_unresolved():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:tns="http://tests.python-zeep.org/"
                targetNamespace="http://tests.python-zeep.org/">
            <element name="foo" type="tns:unresolved">
                <annotation>
                    <documentation>HOI!</documentation>
                </annotation>
            </element>
            <simpleType name="unresolved">
                <restriction base="integer">
                    <minInclusive value="0"/>
                    <maxInclusive value="100"/>
                </restriction>
            </simpleType>
        </schema>
    """)
    schema = parse_schema_node(node)
    assert schema.get_type('{http://tests.python-zeep.org/}unresolved')


def test_element_max_occurs():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
              targetNamespace="http://tests.python-zeep.org/">
            <element name="container">
                <complexType>
                    <sequence>
                        <element name="e1" type="string" />
                        <element name="e2" type="string" maxOccurs="1" />
                        <element name="e3" type="string" maxOccurs="2" />
                        <element name="e4" type="string" maxOccurs="unbounded" />
                    </sequence>
                </complexType>
            </element>
        </schema>
    """)
    schema = parse_schema_node(node)
    elm = schema.get_element('{http://tests.python-zeep.org/}container')
    elements = dict(elm.type.elements)

    assert isinstance(elements['e1'], xsd.Element)
    assert elements['e1'].max_occurs == 1
    assert isinstance(elements['e2'], xsd.Element)
    assert elements['e2'].max_occurs == 1
    assert isinstance(elements['e3'], xsd.Element)
    assert elements['e3'].max_occurs == 2
    assert isinstance(elements['e4'], xsd.Element)
    assert elements['e4'].max_occurs == 'unbounded'


def test_simple_content():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                targetNamespace="http://tests.python-zeep.org/">
            <complexType name="container">
                <simpleContent>
                    <extension base="xsd:string">
                        <attribute name="sizing" type="xsd:string" />
                    </extension>
                </simpleContent>
            </complexType>
        </schema>
    """)
    schema = parse_schema_node(node)
    xsd_type = schema.get_type('{http://tests.python-zeep.org/}container')
    assert xsd_type(10, sizing='qwe')


def test_attribute_optional():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                targetNamespace="http://tests.python-zeep.org/">
          <element name="foo">
            <complexType>
              <xsd:attribute name="base" type="xsd:string" />
            </complexType>
          </element>
        </schema>
    """)
    schema = parse_schema_node(node)
    xsd_element = schema.get_element('{http://tests.python-zeep.org/}foo')
    value = xsd_element()

    node = render_node(xsd_element, value)
    expected = """
      <document>
        <ns0:foo xmlns:ns0="http://tests.python-zeep.org/"/>
      </document>
    """
    assert_nodes_equal(expected, node)


def test_attribute_required():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                targetNamespace="http://tests.python-zeep.org/">
          <element name="foo">
            <complexType>
              <xsd:attribute name="base" use="required" type="xsd:string" />
            </complexType>
          </element>
        </schema>
    """)
    schema = parse_schema_node(node)
    xsd_element = schema.get_element('{http://tests.python-zeep.org/}foo')
    value = xsd_element()

    node = render_node(xsd_element, value)
    expected = """
      <document>
        <ns0:foo xmlns:ns0="http://tests.python-zeep.org/" base=""/>
      </document>
    """
    assert_nodes_equal(expected, node)


def test_attribute_default():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                targetNamespace="http://tests.python-zeep.org/">
          <element name="foo">
            <complexType>
              <xsd:attribute name="base" default="x" type="xsd:string" />
            </complexType>
          </element>
        </schema>
    """)
    schema = parse_schema_node(node)
    xsd_element = schema.get_element('{http://tests.python-zeep.org/}foo')
    value = xsd_element()

    node = render_node(xsd_element, value)
    expected = """
      <document>
        <ns0:foo xmlns:ns0="http://tests.python-zeep.org/" base="x"/>
      </document>
    """
    assert_nodes_equal(expected, node)


def test_attribute_simple_type():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                targetNamespace="http://tests.python-zeep.org/">
            <element name="foo">
              <complexType>
                <attribute name="bar" use="optional">
                 <simpleType>
                  <restriction base="string">
                   <enumeration value="hoi"/>
                   <enumeration value="doei"/>
                  </restriction>
                 </simpleType>
                </attribute>
            </complexType>
          </element>
        </schema>
    """)
    schema = parse_schema_node(node)
    xsd_element = schema.get_element('{http://tests.python-zeep.org/}foo')
    assert xsd_element(bar='hoi')


def test_attribute_any_type():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                targetNamespace="http://tests.python-zeep.org/">
          <element name="foo">
            <complexType>
              <xsd:attribute name="base" type="xsd:anyURI" />
            </complexType>
          </element>
        </schema>
    """)
    schema = parse_schema_node(node)
    xsd_element = schema.get_element('{http://tests.python-zeep.org/}foo')
    value = xsd_element(base='hoi')

    node = render_node(xsd_element, value)
    expected = """
      <document>
        <ns0:foo xmlns:ns0="http://tests.python-zeep.org/" base="hoi"/>
      </document>
    """
    assert_nodes_equal(expected, node)


def test_complex_content_mixed():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:tns="http://tests.python-zeep.org/"
                targetNamespace="http://tests.python-zeep.org/">
          <xsd:element name="foo">
            <xsd:complexType>
              <xsd:complexContent mixed="true">
                <xsd:extension base="xsd:anyType">
                  <xsd:attribute name="bar" type="xsd:anyURI" use="required"/>
                </xsd:extension>
              </xsd:complexContent>
            </xsd:complexType>
          </xsd:element>
        </schema>
    """)
    schema = parse_schema_node(node)
    xsd_element = schema.get_element('{http://tests.python-zeep.org/}foo')
    result = xsd_element('basetype', bar='hoi')

    node = etree.Element('document')
    xsd_element.render(node, result)

    expected = """
      <document>
        <ns0:foo xmlns:ns0="http://tests.python-zeep.org/" bar="hoi">basetype</ns0:foo>
      </document>
    """
    assert_nodes_equal(expected, node)


def test_complex_content_extension():
    node = load_xml("""
        <schema
                xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:tns="http://tests.python-zeep.org/"
                elementFormDefault="qualified"
                targetNamespace="http://tests.python-zeep.org/">
          <complexType name="BaseType" abstract="true">
            <sequence>
              <element name="name" type="xsd:string" minOccurs="0"/>
            </sequence>
          </complexType>
          <complexType name="SubType1">
            <complexContent>
              <extension base="tns:BaseType">
                <attribute name="attr_1" type="xsd:string"/>
                <attribute name="attr_2" type="xsd:string"/>
              </extension>
            </complexContent>
          </complexType>
          <complexType name="SubType2">
            <complexContent>
              <extension base="tns:BaseType">
                <attribute name="attr_a" type="xsd:string"/>
                <attribute name="attr_b" type="xsd:string"/>
                <attribute name="attr_c" type="xsd:string"/>
              </extension>
            </complexContent>
          </complexType>
          <element name="test" type="tns:BaseType"/>
        </schema>
    """)
    schema = parse_schema_node(node)

    record_type = schema.get_type('{http://tests.python-zeep.org/}SubType1')
    assert len(record_type.attributes) == 2
    assert len(record_type.elements) == 1

    record_type = schema.get_type('{http://tests.python-zeep.org/}SubType2')
    assert len(record_type.attributes) == 3
    assert len(record_type.elements) == 1

    xsd_element = schema.get_element('{http://tests.python-zeep.org/}test')
    xsd_type = schema.get_type('{http://tests.python-zeep.org/}SubType2')

    value = xsd_type(attr_a='a', attr_b='b', attr_c='c')
    node = render_node(xsd_element, value)
    expected = """
      <document>
        <ns0:test
            xmlns:ns0="http://tests.python-zeep.org/"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            attr_a="a" attr_b="b" attr_c="c" xsi:type="ns0:SubType2"/>
      </document>
    """
    assert_nodes_equal(expected, node)


def test_simple_content_extension():
    node = load_xml("""
        <schema
                xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:tns="http://tests.python-zeep.org/"
                elementFormDefault="qualified"
                targetNamespace="http://tests.python-zeep.org/">
          <simpleType name="BaseType">
            <restriction base="xsd:integer">
              <minInclusive value="0"/>
              <maxInclusive value="100"/>
            </restriction>
          </simpleType>
          <complexType name="SubType1">
            <simpleContent>
              <extension base="tns:BaseType">
                <attribute name="attr_1" type="xsd:string"/>
                <attribute name="attr_2" type="xsd:string"/>
              </extension>
            </simpleContent>
          </complexType>
          <complexType name="SubType2">
            <simpleContent>
              <extension base="tns:BaseType">
                <attribute name="attr_a" type="xsd:string"/>
                <attribute name="attr_b" type="xsd:string"/>
                <attribute name="attr_c" type="xsd:string"/>
              </extension>
            </simpleContent>
          </complexType>
        </schema>
    """)
    schema = parse_schema_node(node)

    record_type = schema.get_type('{http://tests.python-zeep.org/}SubType1')
    assert len(record_type.attributes) == 2
    assert len(record_type.elements) == 1

    record_type = schema.get_type('{http://tests.python-zeep.org/}SubType2')
    assert len(record_type.attributes) == 3
    assert len(record_type.elements) == 1


def test_list_type():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:tns="http://tests.python-zeep.org/"
                targetNamespace="http://tests.python-zeep.org/">

          <xsd:simpleType name="listOfIntegers">
            <xsd:list itemType="integer" />
          </xsd:simpleType>

          <xsd:element name="foo">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="arg" type="tns:listOfIntegers"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
        </schema>
    """)

    schema = parse_schema_node(node)
    xsd_element = schema.get_element(
        '{http://tests.python-zeep.org/}foo')
    value = xsd_element(arg=[1, 2, 3, 4, 5])

    node = render_node(xsd_element, value)
    expected = """
        <document>
          <ns0:foo xmlns:ns0="http://tests.python-zeep.org/">
            <arg>1 2 3 4 5</arg>
          </ns0:foo>
        </document>
    """
    assert_nodes_equal(expected, node)


def test_list_type_unresolved():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:tns="http://tests.python-zeep.org/"
                targetNamespace="http://tests.python-zeep.org/">

          <xsd:simpleType name="listOfIntegers">
            <xsd:list itemType="tns:something" />
          </xsd:simpleType>

          <xsd:simpleType name="something">
            <xsd:restriction base="xsd:integer" />
          </xsd:simpleType>

          <xsd:element name="foo">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="arg" type="tns:listOfIntegers"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
        </schema>
    """)

    schema = parse_schema_node(node)
    xsd_element = schema.get_element(
        '{http://tests.python-zeep.org/}foo')
    value = xsd_element(arg=[1, 2, 3, 4, 5])

    node = render_node(xsd_element, value)
    expected = """
        <document>
          <ns0:foo xmlns:ns0="http://tests.python-zeep.org/">
            <arg>1 2 3 4 5</arg>
          </ns0:foo>
        </document>
    """
    assert_nodes_equal(expected, node)


def test_list_type_simple_type():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:tns="http://tests.python-zeep.org/"
                targetNamespace="http://tests.python-zeep.org/">

          <xsd:simpleType name="listOfIntegers">
            <xsd:list>
              <simpleType>
                <xsd:restriction base="xsd:integer" />
              </simpleType>
            </xsd:list>
          </xsd:simpleType>

          <xsd:element name="foo">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="arg" type="tns:listOfIntegers"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
        </schema>
    """)

    schema = parse_schema_node(node)
    xsd_element = schema.get_element(
        '{http://tests.python-zeep.org/}foo')
    value = xsd_element(arg=[1, 2, 3, 4, 5])

    node = render_node(xsd_element, value)
    expected = """
        <document>
          <ns0:foo xmlns:ns0="http://tests.python-zeep.org/">
            <arg>1 2 3 4 5</arg>
          </ns0:foo>
        </document>
    """
    assert_nodes_equal(expected, node)


def test_union_type():
    node = load_xml("""
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:tns="http://tests.python-zeep.org/"
                targetNamespace="http://tests.python-zeep.org/">
          <xsd:simpleType name="type">
            <xsd:union memberTypes="xsd:language">
              <xsd:simpleType>
                <xsd:restriction base="xsd:string">
                  <xsd:enumeration value=""/>
                </xsd:restriction>
              </xsd:simpleType>
            </xsd:union>
          </xsd:simpleType>

          <xsd:element name="foo">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="arg" type="tns:type"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
        </schema>
    """)

    schema = parse_schema_node(node)
    xsd_element = schema.get_element('{http://tests.python-zeep.org/}foo')
    assert xsd_element(arg='hoi')


def test_simple_type_restriction():
    node = load_xml("""
        <xsd:schema
            xmlns="http://tests.python-zeep.org/"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://tests.python-zeep.org/"
            elementFormDefault="qualified"
            attributeFormDefault="unqualified">
          <xsd:simpleType name="type_3">
            <xsd:restriction base="type_2"/>
          </xsd:simpleType>
          <xsd:simpleType name="type_2">
            <xsd:restriction base="type_1"/>
          </xsd:simpleType>
          <xsd:simpleType name="type_1">
            <xsd:restriction base="xsd:int">
              <xsd:totalDigits value="3"/>
            </xsd:restriction>
          </xsd:simpleType>
        </xsd:schema>
    """)
    schema = parse_schema_node(node)
    xsd_element = schema.get_type('{http://tests.python-zeep.org/}type_3')
    assert xsd_element(100) == '100'
