import re
from dataclasses import dataclass, field, make_dataclass
from typing import Generator, List
from unittest import TestCase
from xml.etree.ElementTree import QName

from tests.fixtures.books import BookForm
from tests.fixtures.datatypes import Telephone
from tests.fixtures.models import Paragraph, SequentialType, Span, TypeA
from xsdata.exceptions import SerializerError, XmlContextError
from xsdata.formats.dataclass.models.elements import XmlType
from xsdata.formats.dataclass.models.generics import AnyElement, DerivedElement
from xsdata.formats.dataclass.serializers import XmlSerializer
from xsdata.formats.dataclass.serializers.mixins import XmlWriterEvent
from xsdata.models.enums import DataType, QNames
from xsdata.utils.testing import XmlVarFactory


class XmlSerializerTests(TestCase):
    def setUp(self) -> None:
        self.serializer = XmlSerializer()

    def test_wrapper_primitive(self):
        @dataclass
        class PrimitiveWrapper:
            primitive_list: List[str] = field(
                metadata={
                    "wrapper": "PrimitiveList",
                    "type": "Element",
                    "name": "Value",
                }
            )

        obj = PrimitiveWrapper(primitive_list=["Value 1", "Value 2"])
        xml = self.serializer.render(obj)
        expected = r"<PrimitiveWrapper><PrimitiveList><Value>Value 1</Value><Value>Value 2</Value></PrimitiveList></PrimitiveWrapper>"
        self.assertIsNotNone(re.search(expected, xml))

    def test_wrapper_element(self):
        @dataclass
        class ElementObject:
            content: str = field(metadata={"type": "Element"})

        @dataclass
        class ElementWrapper:
            elements: List[ElementObject] = field(
                metadata={"wrapper": "Elements", "type": "Element", "name": "Object"}
            )

        obj = ElementWrapper(
            elements=[ElementObject(content="Hello"), ElementObject(content="World")]
        )
        xml = self.serializer.render(obj)
        expected = "<ElementWrapper><Elements><Object><content>Hello</content></Object><Object><content>World</content></Object></Elements></ElementWrapper>"
        self.assertIsNotNone(re.search(expected, xml))

    def test_wrapper_namespace(self):
        @dataclass
        class NamespaceWrapper:
            items: List[str] = field(
                metadata={
                    "wrapper": "Items",
                    "type": "Element",
                    "name": "item",
                    "namespace": "ns",
                }
            )

        ns_map = {"foo": "ns"}
        obj = NamespaceWrapper(items=["a", "b"])
        xml = self.serializer.render(obj, ns_map=ns_map)
        expected = '<NamespaceWrapper xmlns:foo="ns"><foo:Items><foo:item>a</foo:item><foo:item>b</foo:item></foo:Items></NamespaceWrapper>'
        self.assertIsNotNone(re.search(expected, xml))

    def test_write_object_with_derived_element(self):
        book = BookForm(id="123")
        obj = DerivedElement(qname="item", value=book)

        result = self.serializer.write_object(obj)
        expected = [
            (XmlWriterEvent.START, "item"),
            (XmlWriterEvent.ATTR, "id", "123"),
            (XmlWriterEvent.ATTR, "lang", "en"),
            (XmlWriterEvent.ATTR, QNames.XSI_TYPE, "{urn:books}BookForm"),
            (XmlWriterEvent.END, "item"),
        ]
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

        obj = DerivedElement(qname="{urn:books}BookForm", value=book)
        result = self.serializer.write_object(obj)
        expected = [
            (XmlWriterEvent.START, "{urn:books}BookForm"),
            (XmlWriterEvent.ATTR, "id", "123"),
            (XmlWriterEvent.ATTR, "lang", "en"),
            (XmlWriterEvent.END, "{urn:books}BookForm"),
        ]
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_dataclass(self):
        book = BookForm(id="123", title="Misterioso: A Crime Novel", price=19.5)
        result = self.serializer.write_object(book)
        expected = [
            (XmlWriterEvent.START, "BookForm"),
            (XmlWriterEvent.ATTR, "id", "123"),
            (XmlWriterEvent.ATTR, "lang", "en"),
            (XmlWriterEvent.START, "title"),
            (XmlWriterEvent.DATA, "Misterioso: A Crime Novel"),
            (XmlWriterEvent.END, "title"),
            (XmlWriterEvent.START, "price"),
            (XmlWriterEvent.DATA, "19.5"),
            (XmlWriterEvent.END, "price"),
            (XmlWriterEvent.END, "BookForm"),
        ]
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_dataclass_can_overwrite_params(self):
        book = BookForm(id="123", title="Misterioso: A Crime Novel", price=19.5)
        result = self.serializer.write_dataclass(
            book, "xsdata", "book", True, "foo:book"
        )
        expected = [
            (XmlWriterEvent.START, "book"),
            (XmlWriterEvent.ATTR, "id", "123"),
            (XmlWriterEvent.ATTR, "lang", "en"),
            (XmlWriterEvent.ATTR, QNames.XSI_TYPE, "foo:book"),
            (XmlWriterEvent.ATTR, QNames.XSI_NIL, "true"),
            (XmlWriterEvent.START, "title"),
            (XmlWriterEvent.DATA, "Misterioso: A Crime Novel"),
            (XmlWriterEvent.END, "title"),
            (XmlWriterEvent.START, "price"),
            (XmlWriterEvent.DATA, "19.5"),
            (XmlWriterEvent.END, "price"),
            (XmlWriterEvent.END, "book"),
        ]
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_dataclass_with_no_dataclass(self):
        with self.assertRaises(XmlContextError) as cm:
            next(self.serializer.write_dataclass(1))
        self.assertEqual("Type '<class 'int'>' is not a dataclass.", str(cm.exception))

    def test_write_mixed_content(self):
        var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, qname="a", mixed=True)
        book = BookForm(id="123")
        ebook = DerivedElement("ebook", BookForm(id="123"))
        value = ["text", AnyElement(qname="br"), book, ebook, "tail"]
        result = self.serializer.write_value(value, var, "xsdata")
        expected = [
            (XmlWriterEvent.DATA, "text"),
            (XmlWriterEvent.START, "br"),
            (XmlWriterEvent.DATA, None),
            (XmlWriterEvent.END, "br"),
            (XmlWriterEvent.START, "{xsdata}BookForm"),
            (XmlWriterEvent.ATTR, "id", "123"),
            (XmlWriterEvent.ATTR, "lang", "en"),
            (XmlWriterEvent.END, "{xsdata}BookForm"),
            (XmlWriterEvent.START, "ebook"),
            (XmlWriterEvent.ATTR, "id", "123"),
            (XmlWriterEvent.ATTR, "lang", "en"),
            ("attr", QNames.XSI_TYPE, "{urn:books}BookForm"),
            (XmlWriterEvent.END, "ebook"),
            (XmlWriterEvent.DATA, "tail"),
        ]

        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_data(self):
        var = XmlVarFactory.create(xml_type=XmlType.TEXT, qname="a")
        expected = [(XmlWriterEvent.DATA, "123")]

        result = self.serializer.write_value("123", var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_tokens(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENT, qname="a", tokens_factory=list
        )

        result = self.serializer.write_value(None, var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertListEqual([], list(result))

        result = self.serializer.write_value([], var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertListEqual([], list(result))

        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, []),
            (XmlWriterEvent.END, "a"),
        ]
        var.required = True
        result = self.serializer.write_value([], var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertListEqual(expected, list(result))

        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, ["1", QName("{a}b"), "3"]),
            (XmlWriterEvent.END, "a"),
        ]
        result = self.serializer.write_value([1, QName("{a}b"), 3], var, "xsdata")
        self.assertEqual(expected, list(result))

        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, ["1", "2", "3"]),
            (XmlWriterEvent.END, "a"),
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, ["4", "5", "6"]),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value([[1, 2, 3], [4, 5, 6]], var, "xsdata")
        self.assertEqual(expected, list(result))

        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENT, qname="a", tokens=True, nillable=True
        )
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.ATTR, QNames.XSI_NIL, "true"),
            (XmlWriterEvent.DATA, []),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value([], var, "xsdata")
        self.assertEqual(expected, list(result))

    def test_write_any_type_with_primitive(self):
        var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, qname="a")
        expected = [(XmlWriterEvent.DATA, "str")]

        result = self.serializer.write_value("str", var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_any_type_with_primitive_element(self):
        var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, qname="a", types=(object,))
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, "str"),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value("str", var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_any_type_with_any_element(self):
        var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, qname="a")
        value = AnyElement(
            qname="a",
            text="b",
            tail="c",
            attributes={"d": 1, "e": 2},
            children=[AnyElement(text="g"), "h"],
        )
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.ATTR, "d", 1),
            (XmlWriterEvent.ATTR, "e", 2),
            (XmlWriterEvent.DATA, "b"),
            (XmlWriterEvent.DATA, "g"),
            (XmlWriterEvent.DATA, "h"),
            (XmlWriterEvent.END, "a"),
            (XmlWriterEvent.DATA, "c"),
        ]

        result = self.serializer.write_value(value, var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_any_type_with_derived_element_primitive(self):
        var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, qname="a")
        value = DerivedElement(qname="a", value=1)
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.ATTR, QNames.XSI_TYPE, QName(str(DataType.SHORT))),
            (XmlWriterEvent.DATA, 1),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value(value, var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_any_type_with_derived_element_dataclass(self):
        var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, qname="a")
        value = DerivedElement(
            qname="a", value=BookForm(title="def"), type="{urn:books}BookForm"
        )
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.ATTR, "lang", "en"),
            (XmlWriterEvent.ATTR, QNames.XSI_TYPE, QName("{urn:books}BookForm")),
            (XmlWriterEvent.START, "title"),
            (XmlWriterEvent.DATA, "def"),
            (XmlWriterEvent.END, "title"),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value(value, var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_xsi_type(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENT,
            qname="a",
            name="a",
            types=(BookForm,),
        )
        value = BookForm(id="123")
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.ATTR, "id", "123"),
            (XmlWriterEvent.ATTR, "lang", "en"),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value(value, var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_xsi_type_with_derived_class(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENT,
            qname="a",
            name="a",
            types=(BookForm,),
        )
        ebook = make_dataclass("eBook", [], bases=(BookForm,))
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.ATTR, "id", "123"),
            (XmlWriterEvent.ATTR, "lang", "en"),
            (XmlWriterEvent.ATTR, QNames.XSI_TYPE, QName("eBook")),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value(ebook(id="123"), var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_xsi_type_with_illegal_derived_class(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENT,
            qname="a",
            name="a",
            types=(BookForm,),
        )
        value = TypeA(1)

        result = self.serializer.write_value(value, var, "xsdata")
        with self.assertRaises(SerializerError) as cm:
            list(result)

        self.assertEqual("TypeA is not derived from BookForm", str(cm.exception))

    def test_write_element(self):
        var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, qname="a")
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, "123"),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value("123", var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_element_with_nillable_true(self):
        var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, qname="a", nillable=True)
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.ATTR, QNames.XSI_NIL, "true"),
            (XmlWriterEvent.DATA, "123"),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value("123", var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_element_with_any_type_var(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENT,
            qname="a",
            name="a",
            types=(object,),
            any_type=True,
        )
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.ATTR, QNames.XSI_TYPE, QName(str(DataType.SHORT))),
            (XmlWriterEvent.DATA, "123"),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value(123, var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_element_with_any_type_var_ignore_xs_string(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENT,
            qname="a",
            name="a",
            types=(object,),
            any_type=True,
        )
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, ""),
            (XmlWriterEvent.END, "a"),
        ]
        result = self.serializer.write_value("", var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, "123"),
            (XmlWriterEvent.END, "a"),
        ]
        result = self.serializer.write_value("123", var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_choice_with_derived_primitive_value(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENTS,
            name="compound",
            qname="compound",
            elements={
                "a": XmlVarFactory.create(
                    xml_type=XmlType.ELEMENT, qname="a", types=(int,)
                )
            },
        )
        value = DerivedElement(qname="a", value=1)
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, "1"),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value(value, var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_choice_with_derived_dataclass(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENTS,
            name="compound",
            elements={
                "a": XmlVarFactory.create(
                    xml_type=XmlType.ELEMENT, qname="a", types=(SequentialType,)
                )
            },
        )
        value = DerivedElement(qname="a", value=SequentialType("foo"))
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.ATTR, "a0", "foo"),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value(value, var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_choice_with_generic_object(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENTS,
            name="compound",
            qname="compound",
            elements={
                "a": XmlVarFactory.create(
                    xml_type=XmlType.ELEMENT, qname="a", types=(int,)
                )
            },
        )
        value = AnyElement(qname="a", text="1")
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, "1"),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value(value, var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_choice_with_raw_value(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENTS,
            name="compound",
            qname="compound",
            elements={
                "a": XmlVarFactory.create(
                    xml_type=XmlType.ELEMENT, qname="a", types=(int,)
                ),
                "b": XmlVarFactory.create(
                    xml_type=XmlType.ELEMENT,
                    qname="b",
                    name="b",
                    types=(int,),
                    tokens_factory=list,
                ),
            },
        )
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, "1"),
            (XmlWriterEvent.END, "a"),
            (XmlWriterEvent.START, "b"),
            (XmlWriterEvent.DATA, ["1", "2"]),
            (XmlWriterEvent.END, "b"),
        ]

        result = self.serializer.write_value([1, [1, 2]], var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_choice_when_no_matching_choice_exists_but_value_is_model(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENTS,
            name="compound",
            qname="compound",
            elements={
                "a": XmlVarFactory.create(
                    xml_type=XmlType.WILDCARD, qname="a", types=(object,)
                )
            },
        )

        ebook = make_dataclass("eBook", [], bases=(BookForm,))
        expected = [
            (XmlWriterEvent.START, "eBook"),
            (XmlWriterEvent.ATTR, "lang", "en"),
            (XmlWriterEvent.END, "eBook"),
        ]

        result = self.serializer.write_value(ebook(), var, None)
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_write_choice_when_no_matching_choice_exists(self):
        var = XmlVarFactory.create(
            xml_type=XmlType.ELEMENTS,
            name="compound",
            qname="compound",
            elements={
                "a": XmlVarFactory.create(
                    xml_type=XmlType.ELEMENT, qname="a", types=(float,)
                )
            },
        )

        with self.assertRaises(SerializerError) as cm:
            result = self.serializer.write_value(1, var, "xsdata")
            next(result)

        msg = "XmlElements undefined choice: `compound` for `<class 'int'>`"
        self.assertEqual(msg, str(cm.exception))

    def test_write_value_with_list_value(self):
        var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, qname="a", factory=list)
        value = [True, False]
        expected = [
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, "true"),
            (XmlWriterEvent.END, "a"),
            (XmlWriterEvent.START, "a"),
            (XmlWriterEvent.DATA, "false"),
            (XmlWriterEvent.END, "a"),
        ]

        result = self.serializer.write_value(value, var, "xsdata")
        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))

    def test_next_value(self):
        obj = SequentialType(x0=1, x1=[2, 3, 4, None], x2=[6, 7], x3=[9], x4=10)
        meta = self.serializer.context.build(SequentialType)
        x0 = meta.text
        x1 = next(meta.find_children("x1"))
        x2 = next(meta.find_children("x2"))
        x3 = next(meta.find_children("x3"))
        x4 = next(meta.find_children("x4"))

        actual = self.serializer.next_value(obj, meta)
        expected = [
            (x0, 1),
            (x1, 2),
            (x2, 6),
            (x1, 3),
            (x2, 7),
            (x1, 4),
            (x3, 9),
            (x4, 10),
        ]

        self.assertIsInstance(actual, Generator)
        self.assertEqual(expected, list(actual))

    def test_next_attribute(self):
        obj = SequentialType(a0="foo", a1={"b": "c", "d": "e"})
        meta = self.serializer.context.build(SequentialType)

        actual = self.serializer.next_attribute(obj, meta, False, None, False)
        expected = [
            ("a0", "foo"),
            ("b", "c"),
            ("d", "e"),
        ]

        self.assertIsInstance(actual, Generator)
        self.assertEqual(expected, list(actual))

        actual = self.serializer.next_attribute(obj, meta, True, "xs:bool", False)
        expected.extend(
            [
                (QNames.XSI_TYPE, "xs:bool"),
                (QNames.XSI_NIL, "true"),
            ]
        )
        self.assertEqual(expected, list(actual))

        meta.attributes["a0"].required = False
        meta.attributes["a0"].default = "foo"
        actual = self.serializer.next_attribute(obj, meta, False, None, True)
        expected = [
            ("b", "c"),
            ("d", "e"),
        ]

        self.assertIsInstance(actual, Generator)
        self.assertEqual(expected, list(actual))

    def test_render_mixed_content(self):
        obj = Paragraph()
        obj.content.append(AnyElement(qname="b", text="Mr."))
        obj.content.append(Span("chris"))
        obj.content.append("!")

        self.serializer.pretty_print = False
        result = self.serializer.render(obj).splitlines()
        self.assertEqual("<p><b>Mr.</b><span>chris</span>!</p>", result[1])

        obj = Paragraph()
        obj.content.append("Hi ")
        obj.content.append(AnyElement(qname="b", text="Mr."))
        obj.content.append(Span("chris"))
        obj.content.append("!")
        result = self.serializer.render(obj).splitlines()
        self.assertEqual("<p>Hi <b>Mr.</b><span>chris</span>!</p>", result[1])

    def test_encode_namedtuple(self):
        var = XmlVarFactory.create(types=(Telephone,))
        actual = XmlSerializer.encode(Telephone(30, 234, 56783), var)
        self.assertEqual("30-234-56783", actual)
