import copy
import hashlib
import logging
from typing import Callable, List, Tuple

from maps_adv.adv_store.api.schemas.enums import PlatformEnum, PublicationEnvEnum
from maps_adv.export.lib.config import config
from maps_adv.export.lib.core.enum import (
    CampaignType,
    CreativeType,
    ImageType,
)
from maps_adv.export.lib.core.exception import (
    NotFoundBannerAndLogoText,
    UnsupportedCampaignMultiPlatforms,
)
from maps_adv.export.lib.core.url_signer import ActionUrlSigner
from maps_adv.export.lib.core.utils import generate_image_filename_from_avatar

from .action import action_transform
from .audit_pixel import transform_audit_pixel
from .images import TemplateImageFields

logger = logging.getLogger()


def _images_of_creative(creative: dict, key="images") -> dict:
    return {image["type"]: image for image in creative.get(key, [])}


def _audit_pixel_fields_for_campaign(campaign: dict, ctype: str) -> dict:
    return transform_audit_pixel(
        campaign.get("settings", {}).get("verification_data", {}), ctype
    )


def _limits_and_fields_for_campaign(campaign: dict) -> Tuple[dict, dict]:
    limit_fields_map = dict(
        limitImpressionsPerDay="user_daily_display_limit",
        limitImpressionsTotal="user_display_limit",
        chance="display_chance",
    )

    limits = dict()
    limit_fields = dict()

    for key, field in limit_fields_map.items():
        if field in campaign:
            limit_fields[key] = campaign[field]

    if "display_probability" in campaign:
        limits["displayProbability"] = campaign["display_probability"]

    if PublicationEnvEnum.PRODUCTION in campaign.get("publication_envs", ()):
        impressions = {}

        if (
            campaign.get("total_daily_display_limit") is not None
            and campaign.get("total_displays") is not None
        ):
            impressions["quarterHour"] = _calculateQuarterHourDisplays(campaign)

        if "user_daily_display_limit" in campaign:
            impressions["dailyPerUser"] = campaign["user_daily_display_limit"]

        if impressions:
            limits["impressions"] = impressions

    return limits, limit_fields


def _calculateQuarterHourDisplays(campaign):
    displays_left = campaign["total_daily_display_limit"] - campaign["total_displays"]
    total_display_minutes_today = campaign.get("total_display_minutes_today", 0)
    total_display_minutes_left_today = campaign.get(
        "total_display_minutes_left_today", 0
    )

    if (
        (
            config.EXPERIMENT_QUARTER_HOUR_DISPLAYS_CAMPAIGNS is not None
            and campaign["id"] not in config.EXPERIMENT_QUARTER_HOUR_DISPLAYS_CAMPAIGNS
        )
        or total_display_minutes_today is None
        or total_display_minutes_left_today is None
        or total_display_minutes_today == 0
        or total_display_minutes_left_today == 0
    ):
        return displays_left

    minutesInQuarterHour = 15

    result = int(
        minutesInQuarterHour
        * displays_left
        * config.EXPERIMENT_QUARTER_HOUR_DISPLAYS_COEF
        // total_display_minutes_left_today
    )
    lower_bound = (
        minutesInQuarterHour
        * campaign["total_daily_display_limit"]
        // total_display_minutes_today
    )
    upper_bound = displays_left
    logger.warning(
        f"Computing quarterHour value for campaing {campaign['id']}: "
        f" total_daily_display_limit {campaign['total_daily_display_limit']},"
        f" total_displays {campaign['total_displays']},"
        f" total_display_minutes_today {total_display_minutes_today},"
        f" total_display_minutes_left_today {total_display_minutes_left_today},"
        f" displays_left {displays_left},"
        f" computed result {result},"
        f" upper_bound {upper_bound},"
        f" lower_bound {lower_bound}"
    )
    return min(displays_left, max(lower_bound, result))


def _calculate_creative_id(images: dict, *types) -> str:
    image_hash = hashlib.sha256()
    for image_type in types:
        if image_type in images:
            image_hash.update(str(images[image_type]["group_id"]).encode())
            image_hash.update(images[image_type]["image_name"].encode())
    return image_hash.hexdigest()


def _create_creative_banner(banner: dict) -> dict:
    if banner:
        banner_images = _images_of_creative(banner)
        return dict(
            id=_calculate_creative_id(banner_images, ImageType.BANNER),
            type="banner",
            fields=dict(
                styleBalloonBanner=generate_image_filename_from_avatar(
                    banner_images[ImageType.BANNER]
                )
            ),
        )
    return dict()


def _create_creative_logo_and_text(logo_and_text: dict) -> dict:
    if logo_and_text:
        logo_and_text_images = _images_of_creative(logo_and_text)
        return dict(
            id=_calculate_creative_id(logo_and_text_images, ImageType.LOGO),
            type="logo+text",
            fields=dict(
                text=logo_and_text["text"],
                styleLogo=generate_image_filename_from_avatar(
                    logo_and_text_images[ImageType.LOGO]
                ),
            ),
        )
    return dict()


def _create_creative_audio_banner(audio_banner: dict) -> dict:
    if audio_banner:
        audio_banner_images = _images_of_creative(audio_banner)
        return dict(
            id=_calculate_creative_id(audio_banner_images, ImageType.BANNER),
            type="audio_banner",
            fields=dict(
                audioUrl=audio_banner["audio_file_url"],
                buttonLeftAnchor=audio_banner["left_anchor"],
                styleBalloonBanner=generate_image_filename_from_avatar(
                    audio_banner_images[ImageType.BANNER]
                ),
            ),
        )
    return dict()


def _split_pages_by_groups(pages: List[str]) -> Tuple[List[str], List[str]]:
    pages_production = []
    pages_datatesting = []
    for page in pages:
        if page.count("/"):
            pages_datatesting.append(page)
        else:
            pages_production.append(page)
    return pages_production, pages_datatesting


def _apply_experiment(
    campaign,
    data: List[dict],
    experiment_envs: List[PublicationEnvEnum],
    modifier: Callable[[dict], dict],
) -> List[dict]:
    campaign_envs = campaign["publication_envs"]

    if set(campaign_envs) & set(experiment_envs):
        try:
            result = []
            for item in data:
                pages_production, pages_testing = _split_pages_by_groups(item["pages"])

                for pub_env, pages in (
                    (PublicationEnvEnum.PRODUCTION, pages_production),
                    (PublicationEnvEnum.DATA_TESTING, pages_testing),
                ):
                    if pub_env in campaign_envs:
                        new_item = copy.deepcopy(item)
                        new_item["pages"] = pages

                        if pub_env in experiment_envs:
                            new_item = modifier(new_item)

                        result.append(new_item)

            return result
        except Exception:
            logger.warning(f"Can't apply experiment to campaign {campaign.get('id')}")
            # GEODISPLAY-1438 TODO вернуть репорт взамен sentry

    return data


def _apply_experiment_remove_field_stylepin(campaign, data: List[dict]) -> List[dict]:
    def _remove_field(item: dict) -> dict:
        del item["fields"]["stylePin"]
        return item

    return _apply_experiment(
        campaign,
        data,
        config["EXPERIMENT_WITHOUT_STYLE_PIN_FOR_VIAPOINT"],
        _remove_field,
    )


def _transform_pin_on_route(campaign, ctype: str) -> List[dict]:
    product = "pin_on_route_v2"

    creatives = campaign["creatives"]

    pin = creatives[CreativeType.PIN]
    pin_images = _images_of_creative(pin)

    logo_and_text = creatives.get(CreativeType.LOGO_AND_TEXT)
    banner = creatives.get(CreativeType.BANNER)

    if not any([logo_and_text, banner]):
        raise NotFoundBannerAndLogoText()

    limits, limit_fields = _limits_and_fields_for_campaign(campaign)
    audit_pixel_fields = _audit_pixel_fields_for_campaign(campaign, ctype)

    optional_images = {
        "dust": TemplateImageFields(pin_images, anchor="0.5 0.5", size="18 18"),
        "pin": TemplateImageFields(pin_images, anchor="0.5 0.89", size="32 38"),
        "pin_selected": TemplateImageFields(
            pin_images, anchor="0.5 0.94", size="60 68"
        ),
    }

    optional_fields = {}
    optional_fields.update(limit_fields)
    optional_fields.update(audit_pixel_fields)

    if banner["title"]:
        optional_fields["title"] = banner["title"]

    if banner["show_ads_label"]:
        optional_fields["isAds"] = "true"

    return [
        dict(
            pages=campaign["pages"],
            places=campaign["places"],
            polygons=[],
            disclaimer=banner["disclaimer"],
            fields=dict(
                campaignId=campaign["id"],
                product=product,
                chains="",  # TODO(megadiablo) Удалить после релиза
                ageCategory="",
                hasDiscounts="false",
                pinTitle=pin["title"],
                pinSubtitle=pin["subtitle"],
                stylePin=generate_image_filename_from_avatar(
                    pin_images[ImageType.PIN_ROUND]
                ),
                stylePinLeft=generate_image_filename_from_avatar(
                    pin_images[ImageType.PIN_LEFT]
                ),
                stylePinRight=generate_image_filename_from_avatar(
                    pin_images[ImageType.PIN_RIGHT]
                ),
                **optional_images["dust"].generate("Dust", ImageType.DUST),
                **optional_images["dust"].generate("DustHover", ImageType.DUST_HOVER),
                **optional_images["dust"].generate(
                    "DustVisited", ImageType.DUST_VISITED
                ),
                **optional_images["pin"].generate("Icon", ImageType.PIN),
                **optional_images["pin"].generate("IconHover", ImageType.PIN_HOVER),
                **optional_images["pin"].generate("IconVisited", ImageType.PIN_VISITED),
                **optional_images["pin_selected"].generate(
                    "Selected", ImageType.PIN_SELECTED
                ),
                **optional_fields,
            ),
            creatives=list(
                filter(
                    bool,
                    [
                        _create_creative_logo_and_text(logo_and_text),
                        _create_creative_banner(banner),
                    ],
                )
            ),
            actions=[action_transform(action) for action in campaign["actions"]],
            limits=limits,
            target=campaign.get("targeting", {}),
            log_info=dict(
                advertiserId=None, campaignId=str(campaign["id"]), product=product
            ),
            cost=campaign.get("cost"),
        )
    ]


def _transform_pin_on_route_navi(campaign, ctype: str) -> List[dict]:
    def _transform_pin(item: dict) -> dict:
        fields = item["fields"]
        fields["styleIcon"] = fields["stylePin"]
        fields["styleIconHover"] = fields["stylePin"]
        fields["styleIconVisited"] = fields["stylePin"]

        for key in ("sizeIcon", "sizeIconHover", "sizeIconVisited"):
            fields[key] = "48 48"

        for key in ("anchorIcon", "anchorIconHover", "anchorIconVisited"):
            fields[key] = "0.5 0.5"

        return item

    return _apply_experiment(
        campaign,
        _transform_pin_on_route(campaign, ctype),
        config["MONKEY_PATCH_NAVI_WITH_ROUND_PIN_FOR_PIN_ON_ROUTE"],
        _transform_pin,
    )


def _transform_zero_speed_banner(
    campaign, navi_signer: ActionUrlSigner, ctype: str
) -> List[dict]:
    product = "zero_speed_banner"

    audio_banner = campaign["creatives"].get(CreativeType.AUDIO_BANNER)
    banner = campaign["creatives"][CreativeType.BANNER]

    limits, limit_fields = _limits_and_fields_for_campaign(campaign)
    audit_pixel_fields = _audit_pixel_fields_for_campaign(campaign, ctype)

    optional_fields = {}
    optional_fields.update(limit_fields)
    optional_fields.update(audit_pixel_fields)

    if banner["show_ads_label"]:
        optional_fields["isAds"] = "true"

    terms = banner.get("terms")
    if terms:
        optional_fields["terms"] = terms

    return [
        dict(
            pages=campaign["pages"],
            places=[],
            polygons=campaign["polygons"],
            disclaimer=banner["disclaimer"],
            fields=dict(
                ageCategory="",
                campaignId=campaign["id"],
                product=product,
                title=banner["title"],
                description=banner["description"],
                stylePin="",  # https://st.yandex-team.ru/MOBNAVI-13395
                **optional_fields,
            ),
            creatives=list(
                filter(
                    bool,
                    [
                        _create_creative_audio_banner(audio_banner),
                        _create_creative_banner(banner),
                    ],
                )
            ),
            actions=[
                action_transform(action, navi_signer) for action in campaign["actions"]
            ],
            limits=limits,
            target=campaign["targeting"],
            log_info=dict(
                advertiserId=None, campaignId=str(campaign["id"]), product=product
            ),
            cost=campaign.get("cost"),
        )
    ]


def _transform_overview_banner(
    campaign, navi_signer: ActionUrlSigner, ctype
) -> List[dict]:
    product = "overview_banner"

    banner = campaign["creatives"][CreativeType.BANNER]
    banner_images = _images_of_creative(banner)

    limits, limit_fields = _limits_and_fields_for_campaign(campaign)
    audit_pixel_fields = _audit_pixel_fields_for_campaign(campaign, ctype)

    optional_fields = {}
    optional_fields.update(limit_fields)
    optional_fields.update(audit_pixel_fields)

    if banner["show_ads_label"]:
        optional_fields["isAds"] = "true"

    return [
        dict(
            pages=campaign["pages"],
            places=[],
            polygons=campaign["polygons"],
            disclaimer=banner["disclaimer"],
            fields=dict(
                ageCategory="",
                campaignId=campaign["id"],
                product=product,
                title=banner["title"],
                description=banner["description"],
                stylePin="",  # https://st.yandex-team.ru/MOBNAVI-13395
                **optional_fields,
            ),
            creatives=[
                dict(
                    id=_calculate_creative_id(banner_images, ImageType.BANNER),
                    type="banner",
                    fields=dict(
                        styleBalloonBanner=generate_image_filename_from_avatar(
                            banner_images[ImageType.BANNER]
                        )
                    ),
                )
            ],
            actions=[
                action_transform(action, navi_signer) for action in campaign["actions"]
            ],
            limits=limits,
            target=campaign["targeting"],
            log_info=dict(
                advertiserId=None, campaignId=str(campaign["id"]), product=product
            ),
            cost=campaign.get("cost"),
        )
    ]


def _transform_billboard(campaign, ctype: str) -> List[dict]:
    product = "billboard"

    banner = campaign["creatives"][CreativeType.BANNER]
    banner_images = _images_of_creative(banner)

    billboard_creative = campaign["creatives"][CreativeType.BILLBOARD]
    billboard_images = _images_of_creative(billboard_creative, key="images_v2")
    if not billboard_images:
        billboard_images = _images_of_creative(billboard_creative, key="images")

    limits, limit_fields = _limits_and_fields_for_campaign(campaign)
    audit_pixel_fields = _audit_pixel_fields_for_campaign(campaign, ctype)

    optional_fields = {}
    optional_fields.update(limit_fields)
    optional_fields.update(audit_pixel_fields)

    if banner["show_ads_label"]:
        optional_fields["isAds"] = "true"

    optional_fields |= {
        k: v
        for k, v in {
            "pinTitle": billboard_creative.get("title"),
            "pinSubtitle": billboard_creative.get("description"),
            "description": banner.get("description"),
        }.items()
        if v
    }

    return [
        dict(
            pages=campaign["pages"],
            places=campaign["places"],
            polygons=[],
            disclaimer=banner["disclaimer"],
            fields=dict(
                ageCategory="",
                campaignId=campaign["id"],
                product=product,
                title=banner["title"],
                stylePin=generate_image_filename_from_avatar(
                    billboard_images[ImageType.PIN]
                ),
                **optional_fields,
            ),
            creatives=[
                dict(
                    id=_calculate_creative_id(banner_images, ImageType.BANNER),
                    type="banner",
                    fields=dict(
                        styleBalloonBanner=generate_image_filename_from_avatar(
                            banner_images[ImageType.BANNER]
                        )
                    ),
                )
            ],
            actions=[action_transform(action) for action in campaign["actions"]],
            limits=limits,
            target=campaign.get("targeting", {}),
            log_info=dict(
                advertiserId=None, campaignId=str(campaign["id"]), product=product
            ),
            cost=campaign.get("cost"),
        )
    ]


def _transform_metro_route_banner(campaign, ctype: str) -> List[dict]:
    product = "metro_banner"
    images = _images_of_creative(campaign["creatives"][CreativeType.BANNER])
    limits, limit_fields = _limits_and_fields_for_campaign(campaign)
    audit_pixel_fields = _audit_pixel_fields_for_campaign(campaign, ctype)

    banner = campaign["creatives"][CreativeType.BANNER]

    optional_fields = {}
    optional_fields.update(limit_fields)
    optional_fields.update(audit_pixel_fields)

    if banner["show_ads_label"]:
        optional_fields["isAds"] = "true"

    return [
        dict(
            pages=campaign["pages"],
            places=campaign["places"],
            polygons=campaign["polygons"],
            disclaimer=banner["disclaimer"],
            fields=dict(
                ageCategory="",
                campaignId=campaign["id"],
                product=product,
                title=banner["title"],
                description=banner["description"],
                styleBanner=generate_image_filename_from_avatar(
                    images[ImageType.BANNER]
                ),
                **optional_fields,
            ),
            creatives=[
                dict(
                    id=_calculate_creative_id(images, ImageType.BIG_BANNER),
                    type="metro_big_banner",
                    fields=dict(
                        styleBigBanner=generate_image_filename_from_avatar(
                            images[ImageType.BIG_BANNER]
                        )
                    ),
                )
            ],
            actions=[action_transform(action) for action in campaign["actions"]],
            limits=limits,
            target=campaign.get("targeting", {}),
            log_info=dict(
                advertiserId=None, campaignId=str(campaign["id"]), product=product
            ),
            cost=campaign.get("cost"),
        )
    ]


def _transform_metro_promocode(campaign, ctype: str) -> List[dict]:
    product = "metro_promocode"
    images = _images_of_creative(campaign["creatives"][CreativeType.BANNER])
    limits, limit_fields = _limits_and_fields_for_campaign(campaign)
    audit_pixel_fields = _audit_pixel_fields_for_campaign(campaign, ctype)

    banner = campaign["creatives"][CreativeType.BANNER]

    optional_fields = {}
    optional_fields.update(limit_fields)
    optional_fields.update(audit_pixel_fields)

    if banner["show_ads_label"]:
        optional_fields["isAds"] = "true"

    terms = banner.get("terms")
    if terms:
        optional_fields["terms"] = terms

    return [
        dict(
            pages=campaign["pages"],
            places=[],
            polygons=campaign["polygons"],
            disclaimer=banner["disclaimer"],
            fields=dict(
                ageCategory="",
                campaignId=campaign["id"],
                product=product,
                title=banner["title"],
                description=banner["description"],
                **optional_fields,
            ),
            creatives=[
                dict(
                    id=_calculate_creative_id(images, ImageType.LOGO, ImageType.BANNER),
                    type="banner",
                    fields=dict(
                        logo=generate_image_filename_from_avatar(
                            images[ImageType.LOGO]
                        ),
                        banner=generate_image_filename_from_avatar(
                            images[ImageType.BANNER]
                        ),
                    ),
                )
            ],
            actions=[action_transform(action) for action in campaign["actions"]],
            limits=limits,
            target=campaign.get("targeting", {}),
            log_info=dict(
                advertiserId=None, campaignId=str(campaign["id"]), product=product
            ),
            cost=campaign.get("cost"),
        )
    ]


def _transform_route_via_point(campaign, ctype: str) -> List[dict]:
    product = "route_via_point"

    creatives = campaign["creatives"]

    via_point = creatives[CreativeType.VIA_POINT]
    via_point_images = _images_of_creative(via_point)

    limits, limit_fields = _limits_and_fields_for_campaign(campaign)

    # This field no supporting for campaign type of route via point
    limit_fields.pop("chance", None)

    optional_fields = {}
    optional_fields.update(limit_fields)

    result = [
        dict(
            pages=campaign["pages"],
            places=campaign["places"],
            polygons=[],
            disclaimer="",
            fields=dict(
                campaignId=campaign["id"],
                product=product,
                viaActiveTitle=via_point["button_text_active"],
                viaInactiveTitle=via_point["button_text_inactive"],
                viaDescription=via_point["description"],
                anchorViaPin="0.5 0.5",
                sizeViaPin="32 32",
                styleViaPin=generate_image_filename_from_avatar(
                    via_point_images[ImageType.PIN]
                ),
                # TODO(megadiablo) Убрать костыль для навика, когда процент
                #                  зааффекченых навигаторов упадет достаточно
                #                  низко MOBNAVI-16318, GEOADVDEV-2480
                stylePin=generate_image_filename_from_avatar(
                    via_point_images[ImageType.PIN]
                ),
                **optional_fields,
            ),
            creatives=[],
            actions=[],
            limits=limits,
            target=campaign.get("targeting", {}),
            log_info=dict(
                advertiserId=None, campaignId=str(campaign["id"]), product=product
            ),
            cost=campaign.get("cost"),
        )
    ]

    return _apply_experiment_remove_field_stylepin(campaign, result)


async def pin_data_transform(
    campaign: dict, navi_signer: ActionUrlSigner, ctype: str
) -> List[dict]:
    platforms = {
        CampaignType.PIN_ON_ROUTE: {
            PlatformEnum.NAVI: _transform_pin_on_route_navi,
            PlatformEnum.MAPS: _transform_pin_on_route,
        },
        CampaignType.ZERO_SPEED_BANNER: {
            PlatformEnum.NAVI: lambda action, ctype: _transform_zero_speed_banner(
                action, navi_signer, ctype
            )
        },
        CampaignType.OVERVIEW_BANNER: {
            PlatformEnum.NAVI: lambda action, ctype: _transform_overview_banner(
                action, navi_signer, ctype
            )
        },
        CampaignType.BILLBOARD: {
            PlatformEnum.NAVI: _transform_billboard,
            PlatformEnum.MAPS: _transform_billboard,
        },
        CampaignType.CATEGORY_SEARCH: {},
        CampaignType.CATEGORY: {},
        CampaignType.PIN_SEARCH: {},
        CampaignType.ROUTE_BANNER: {PlatformEnum.METRO: _transform_metro_route_banner},
        CampaignType.ROUTE_VIA_POINT: {PlatformEnum.NAVI: _transform_route_via_point},
        CampaignType.PROMOCODE: {PlatformEnum.METRO: _transform_metro_promocode},
    }[campaign["campaign_type"]]

    if len(campaign["platforms"]) != 1:
        raise UnsupportedCampaignMultiPlatforms()

    transform = platforms.get(campaign["platforms"][0])
    if transform:
        return transform(campaign, ctype)

    return []
