import copy
from itertools import chain
from operator import itemgetter
from typing import Iterable, List

from maps_adv.adv_store.api.schemas.enums import PublicationEnvEnum
from maps_adv.export.lib.core.enum import CampaignType, CreativeType
from maps_adv.export.lib.core.exception import CrossCampaignsForMerging


def _split_campaign_type_of_category_search(campaign: dict) -> List[dict]:
    if campaign["campaign_type"] != CampaignType.CATEGORY_SEARCH:
        return [campaign]

    category = copy.deepcopy(campaign)
    pin_search = copy.deepcopy(campaign)

    creative_icon = category["creatives"][CreativeType.ICON]

    category["campaign_type"] = CampaignType.CATEGORY
    category["creatives"].pop(CreativeType.TEXT, None)

    category_organizations = (
        category["placing"].get("organizations", {}).get("permalinks", [])
    )
    category["placing"]["organizations"] = dict(permalinks=category_organizations)

    icon_organizations = creative_icon["organizations"] = creative_icon.get(
        "organizations", []
    )

    for creative in category["creatives"].pop(CreativeType.PIN_SEARCH, []):
        icon_organizations.extend(creative.get("organizations", []))

    pin_search["campaign_type"] = CampaignType.PIN_SEARCH
    pin_search["placing"]["organizations"] = dict(permalinks=[])
    pin_search["creatives"].pop(CreativeType.ICON, None)

    return [category, pin_search]


def _merge_campaign_type_of_category(parent: dict, campaigns: Iterable[dict]) -> dict:
    parent_creatives = parent["creatives"]
    parent_organizations = (
        parent["placing"].get("organizations", {}).get("permalinks", [])
    )
    parent["placing"]["organizations"] = dict(permalinks=parent_organizations)

    parent_icon_organizations = parent_creatives[CreativeType.ICON].get(
        "organizations", []
    )
    parent_creatives[CreativeType.ICON]["organizations"] = parent_icon_organizations

    for campaign in campaigns:
        if campaign["campaign_type"] == CampaignType.CATEGORY:
            parent_organizations.extend(
                campaign["placing"].get("organizations", {}).get("permalinks", [])
            )
            parent_icon_organizations.extend(
                campaign["creatives"][CreativeType.ICON].get("organizations", [])
            )

    return parent


def _split_campaign_by_platform(campaign: dict) -> List[dict]:

    if len(campaign["platforms"]) == 1:
        return [campaign]

    return list(
        map(
            lambda platform: copy.deepcopy(campaign) | {"platforms": [platform]},
            campaign["platforms"],
        )
    )


def _split_billboard_campaign(campaign: dict) -> List[dict]:
    if campaign["campaign_type"] != CampaignType.BILLBOARD:
        return [campaign]

    billboards = campaign["creatives"][CreativeType.BILLBOARD]
    if not isinstance(billboards, list):
        billboards = [billboards]

    if len(billboards) == 1:
        return [campaign]

    return list(
        map(
            lambda billboard: copy.deepcopy(campaign)
            | {
                "creatives": campaign["creatives"]
                | {CreativeType.BILLBOARD: billboard | {"split": True}}
            },
            billboards,
        )
    )


def _split_campaigns(splitter):
    def wrapper(campaigns: list) -> list:
        result_campaigns = []

        for campaign in campaigns:
            result_campaigns.extend(splitter(campaign))

        return result_campaigns

    return wrapper


split_category_search_campaigns = _split_campaigns(
    splitter=_split_campaign_type_of_category_search
)
split_campaigns_by_platform = _split_campaigns(splitter=_split_campaign_by_platform)
split_billboard_campaigns = _split_campaigns(splitter=_split_billboard_campaign)


def merge_category_campaigns(campaigns: list, parents: dict):
    result_campaigns = []
    children = {item: None for item in chain.from_iterable(parents.values())}

    cross_campaigns = set(parents.keys()) & set(children.keys())
    if cross_campaigns:
        raise CrossCampaignsForMerging(
            "Parents and children campaigns crossing: %s", cross_campaigns
        )

    for campaign in campaigns:
        campaign_id = campaign["id"]
        campaign_type = campaign["campaign_type"]
        if campaign_id in children and campaign_type == CampaignType.CATEGORY:
            children[campaign_id] = campaign

    for campaign in campaigns:
        campaign_id = campaign["id"]
        campaign_type = campaign["campaign_type"]

        if campaign_type == CampaignType.CATEGORY and campaign_id in parents:
            result_campaigns.append(
                _merge_campaign_type_of_category(
                    campaign,
                    [
                        children[child]
                        for child in parents[campaign_id]
                        if children.get(child)
                    ],
                )
            )

        elif campaign_type != CampaignType.CATEGORY or campaign_id not in children:
            result_campaigns.append(campaign)

    return result_campaigns


async def filter_update_event_limit_excess(campaigns: list) -> list:
    for campaign in campaigns:

        displays_exceeded = (
            campaign.get("total_displays") is not None
            and campaign.get("total_daily_display_limit") is not None
            and campaign["total_displays"] >= campaign["total_daily_display_limit"]
        )

        actions_exceeded = (
            campaign.get("total_actions") is not None
            and campaign.get("total_daily_action_limit") is not None
            and campaign["total_actions"] >= campaign["total_daily_action_limit"]
        )

        campaign["publication_envs"] = list(
            filter(
                lambda env: env != PublicationEnvEnum.PRODUCTION
                or (not displays_exceeded and not actions_exceeded),
                campaign["publication_envs"],
            )
        )

    return list(filter(itemgetter("publication_envs"), campaigns))


async def drop_campaigns_with_no_page_ids(campaigns: list) -> list:
    return list(filter(lambda campaign: bool(campaign.get("pages")), campaigns))
