import json
from collections import OrderedDict
from operator import itemgetter
from typing import Callable, Iterable, List

from maps_adv.export.lib.core.xml import XmlDoc, XmlElement, helper


class PinDataList:
    def __init__(self, pin_data_list: List[dict]):
        self._pin_data_list = pin_data_list

    @classmethod
    def from_iterable(cls, data: Iterable) -> "PinDataList":
        return cls(list(data))

    @classmethod
    def _targeting_as_xml(cls, tag: Callable, targeting: dict):
        if targeting.get("not", False):
            with tag("Not"):
                data = targeting.copy()
                data["not"] = False

                cls._targeting_as_xml(tag, data)

        elif targeting:
            tag_name = targeting["tag"]
            if tag_name in {"and", "or"}:
                with tag(tag_name.capitalize()):
                    for item in targeting.get("items", []):
                        cls._targeting_as_xml(tag, item)

            else:
                attrs = targeting.get("attributes", {})
                content = targeting.get("content", [])

                if len(content) > 1:
                    with tag("Or"):
                        for text in content:
                            tag(tag_name, text=text, **attrs)

                elif len(content) == 1:
                    tag(tag_name, text=content[0], **attrs)

                elif attrs:
                    tag(tag_name, **attrs)

    def to_xml(self) -> XmlElement:
        doc, tag, text, attrs, append = XmlDoc("PinDataList").ttaa()

        for pin_data in self._pin_data_list:
            with tag("PinData"):
                for page_id in pin_data["pages"]:
                    tag("pageId", text=page_id)

                tag("disclaimer", text=pin_data["disclaimer"])
                if "cost" in pin_data and pin_data["cost"] is not None:
                    tag("cost", text=pin_data["cost"])

                with tag("Places"):
                    for place_id in sorted(pin_data["places"]):
                        tag("id", text=place_id)

                with tag("Polygons"):
                    for polygon_id in sorted(pin_data["polygons"]):
                        tag("id", text=polygon_id)

                with tag("Tags"):
                    for name, value in sorted(pin_data["fields"].items()):
                        value = value if isinstance(value, list) else [value]
                        for el in value:
                            tag("field", name=name, text=el)

                with tag("Creatives"):
                    for creative in pin_data["creatives"]:
                        with tag("Creative"):
                            tag("id", text=creative["id"])
                            tag("type", text=creative["type"])

                            fields = sorted(creative["fields"].items())
                            for name, text in fields:
                                tag("field", name=name, text=text)

                with tag("Actions"):
                    for action in sorted(pin_data["actions"], key=itemgetter("type")):
                        with tag("Action"):
                            tag("type", text=action["type"])

                            fields = sorted(action["fields"].items())
                            for name, text in fields:
                                tag("field", name=name, text=text)

                with tag("Target"):
                    self._targeting_as_xml(tag, pin_data["target"])

                with tag("Limits"):
                    limits = sorted(pin_data["limits"].items())
                    for name, text in limits:
                        if name == "impressions":
                            with tag("Impressions"):
                                for name, text in text.items():
                                    tag(name, text=text)
                        else:
                            tag(name, text=text)

                log_info = OrderedDict(
                    sorted(
                        map(
                            lambda item: (item[0], str(item[1])),
                            pin_data["log_info"].items(),
                        )
                    )
                )
                tag("logInfo", text=json.dumps(log_info))

        return doc.result

    def __str__(self) -> str:
        return helper.xml_to_string(self.to_xml())
