from asyncio import gather
from collections import defaultdict
from typing import Iterable, List

from maps_adv.adv_store.client import Client
from maps_adv.common.helpers.enums import CampaignTypeEnum
from maps_adv.export.lib.core.enum import (
    ActionType,
    CampaignType,
    CreativeType,
    ImageType,
)


enum_map = {
    "creative_type": {
        "billboard": CreativeType.BILLBOARD,
        "pin": CreativeType.PIN,
        "logo_and_text": CreativeType.LOGO_AND_TEXT,
        "banner": CreativeType.BANNER,
        "icon": CreativeType.ICON,
        "text": CreativeType.TEXT,
        "pin_search": CreativeType.PIN_SEARCH,
        "via_point": CreativeType.VIA_POINT,
        "audio_banner": CreativeType.AUDIO_BANNER,
    },
    "action_type": {
        "open_site": ActionType.OPEN_SITE,
        "search": ActionType.SEARCH,
        "phone_call": ActionType.PHONE_CALL,
        "download_app": ActionType.DOWNLOAD_APP,
        "promocode": ActionType.PROMOCODE,
        "resolve_uri": ActionType.RESOLVE_URI,
        "add_point_to_route": ActionType.ADD_POINT_TO_ROUTE,
    },
    "image_type": {
        "logo": ImageType.LOGO,
        "banner": ImageType.BANNER,
        "big_banner": ImageType.BIG_BANNER,
        "icon": ImageType.CATEGORY,
        "category": ImageType.CATEGORY,
        "dust": ImageType.DUST,
        "dust_hover": ImageType.DUST_HOVER,
        "dust_visited": ImageType.DUST_VISITED,
        "pin": ImageType.PIN,
        "pin_hover": ImageType.PIN_HOVER,
        "pin_visited": ImageType.PIN_VISITED,
        "pin_selected": ImageType.PIN_SELECTED,
        "pin_round": ImageType.PIN_ROUND,
        "pin_left": ImageType.PIN_LEFT,
        "pin_right": ImageType.PIN_RIGHT,
    },
    "campaign_type": {
        CampaignTypeEnum.BILLBOARD: CampaignType.BILLBOARD,
        CampaignTypeEnum.CATEGORY_SEARCH: CampaignType.CATEGORY_SEARCH,
        CampaignTypeEnum.OVERVIEW_BANNER: CampaignType.OVERVIEW_BANNER,
        CampaignTypeEnum.PIN_ON_ROUTE: CampaignType.PIN_ON_ROUTE,
        CampaignTypeEnum.PROMOCODE: CampaignType.PROMOCODE,
        CampaignTypeEnum.ROUTE_BANNER: CampaignType.ROUTE_BANNER,
        CampaignTypeEnum.VIA_POINTS: CampaignType.ROUTE_VIA_POINT,
        CampaignTypeEnum.ZERO_SPEED_BANNER: CampaignType.ZERO_SPEED_BANNER,
    },
}


class FetchCampaignsStep:
    # Start of Yandex.Audience range in Big Brother
    AUDIENCE_START_NUMBER = 2 * 10**9

    def __init__(self, config):
        self._client = Client(config.ADV_STORE_URL)

    async def __call__(self, campaigns):
        async with self._client as adv_store_client:
            data = await adv_store_client.list_campaigns_for_export()

        if data["campaigns"]:
            await gather(
                *[
                    self.transform_campaign_data(campaign)
                    for campaign in data["campaigns"]
                ]
            )

        return data["campaigns"]

    async def transform_campaign_data(self, campaign: dict):
        campaign["campaign_type"] = enum_map["campaign_type"][campaign["campaign_type"]]
        campaign["places"] = []
        campaign["companies"] = []
        campaign["creatives"] = self.group_by_enum_creative_type(campaign["creatives"])
        campaign["actions"] = self.transform_action_data(campaign["actions"])

        if "area" in campaign["placing"]:
            self.transform_placing_areas(campaign["placing"]["area"])

        campaign["targeting"] = self.transform_targeting(campaign.get("targeting"))

        if campaign.get("settings") is None:
            campaign["settings"] = {}

    @classmethod
    def replace_image_type_with_enum(cls, images: List[dict]):
        enum_image_type = enum_map["image_type"]
        unknown_images = []
        for image in images:
            image["type"] = enum_image_type.get(image["type"])
            if image["type"] is None:
                unknown_images.append(image)

        for image in unknown_images:
            images.remove(image)

    def update_action(self, action: dict) -> dict:
        updated_action = {}
        action = {action.pop("type_"): action}
        for action_type, action_data in action.items():
            if action_type not in enum_map["action_type"]:
                updated_action[action_type] = action_data
                continue
            updated_action["type"] = enum_map["action_type"][action_type]
            updated_action.update(**action_data)

        return updated_action

    def transform_action_data(self, actions: Iterable[dict]) -> List[dict]:
        updated_actions = []
        for action in actions:
            updated_action = self.update_action(action)
            updated_actions.append(updated_action)

        return updated_actions

    def group_by_enum_creative_type(self, creatives: Iterable[dict]) -> dict:
        creatives_dict = defaultdict(list)

        for creative in creatives:
            for image_field in ("images", "images_v2"):
                if image_field in creative:
                    self.replace_image_type_with_enum(creative[image_field])
            creative_type = enum_map["creative_type"][creative.pop("type_")]
            creatives_dict[creative_type].append(creative)

        return {
            k: v if len(v) > 1 or k in (CreativeType.PIN_SEARCH,) else v[0]
            for k, v in creatives_dict.items()
        }

    @classmethod
    def transform_placing_areas(cls, area: dict):
        for polygon in area["areas"]:
            for point in polygon["points"]:
                point["latitude"] = point.pop("lat")
                point["longitude"] = point.pop("lon")

    @classmethod
    def transform_targeting(cls, targeting: dict) -> dict:
        if targeting:
            tag = targeting["tag"]

            attrs = targeting.get("attributes")
            if attrs is None:
                attrs = targeting["attributes"] = {}

            content = targeting.get("content")
            if content is None:
                targeting["content"] = []
            elif isinstance(content, str):
                targeting["content"] = [content]

            if tag == "audience" and "id" in attrs:
                attrs["id"] = str(int(attrs["id"]) + cls.AUDIENCE_START_NUMBER)

            if "items" in targeting:
                for item in targeting["items"]:
                    cls.transform_targeting(item)

        return targeting
