import collections
import inspect
import os
import tempfile

from maps_adv.common.third_party_clients.juggler import JugglerClient
from maps_adv.export.lib.core.client import SandboxClient
from maps_adv.export.lib.core.url_signer import ActionUrlSigner
from maps_adv.export.lib.pipeline.exceptions import StepException
from maps_adv.export.lib.pipeline.filter import (
    AuditPixelCampaignTypeFilter,
    CampaignWithoutPlacesFilter,
)
from maps_adv.export.lib.pipeline.param import Param
from maps_adv.export.lib.pipeline.resolver import styles_resolver
from maps_adv.export.lib.pipeline.steps import (
    AddStatisticsStep,
    ApplyOrderDiscountsStep,
    BillingStep,
    FetchCampaignsStep,
    MergeCategoryCampaignsStep,
    ResolveOrganisationsStep,
    ResolvePagesStep,
    ResolvePointsStep,
    ResolvePolygonsStep,
    SearchTagsStep,
)
from maps_adv.export.lib.pipeline.transform import (
    filter_update_event_limit_excess,
    drop_campaigns_with_no_page_ids,
    split_billboard_campaigns,
    split_category_search_campaigns,
    split_campaigns_by_platform,
)
from maps_adv.export.lib.pipeline.validator import (
    CrossPermalinkValidator,
    ValidateXmlSchema,
)
from maps_adv.export.lib.pipeline.xml import prepare_xml_file
from maps_adv.common.helpers.timing import TimeDiff, async_time_diff


@async_time_diff
async def export(config, cache_folder: str):
    campaigns = []

    polygons = Param()
    places = Param()
    tags = Param()

    campaigns, broken_info = await _run_pipeline(
        [
            FetchCampaignsStep(config),
            BillingStep(config),
            ApplyOrderDiscountsStep(config),
            AddStatisticsStep(config),
            filter_update_event_limit_excess,
            ResolvePolygonsStep(polygons),
            split_category_search_campaigns,
            MergeCategoryCampaignsStep(config),
            AuditPixelCampaignTypeFilter(),
            ResolvePointsStep(places, cache_folder, config),
            ResolveOrganisationsStep(places, config),
            split_billboard_campaigns,
            split_campaigns_by_platform,
            CrossPermalinkValidator(),
            CampaignWithoutPlacesFilter(),
            SearchTagsStep(tags),
            ResolvePagesStep(config),
            drop_campaigns_with_no_page_ids,
            styles_resolver,
        ],
        campaigns,
    )

    data = dict(
        campaigns=campaigns,
        merge_category_campaigns=config.MONKEY_PATCH_MERGE_CATEGORY_CAMPAIGNS,
        places=places.value,
        polygons=polygons.value,
        search_tags=tags.value,
    )

    juggler_client = JugglerClient(config.JUGGLER_EVENTS_URL, config.NANNY_SERVICE_ID)

    with tempfile.TemporaryDirectory() as tmp_dir:
        work_directory = os.path.join(tmp_dir, config.SANDBOX_EXPORT_ROOT_DIRNAME)
        os.mkdir(work_directory)

        attrs = await prepare_xml_file(
            data,
            validators=[ValidateXmlSchema.from_config(config)],
            filename=os.path.join(work_directory, config.FILENAME_XML),
            navi_signer=ActionUrlSigner(
                config.NAVI_CLIENT_ID, config.NAVI_SIGNATURE_FILE
            ),
            ctype=config.INSTANCE_TAG_CTYPE,
        )
        attrs["released"] = config.SANDBOX_EXPORT_RELEASE_TYPE

        await SandboxClient(config, attrs, work_directory, juggler_client).upload()

    await juggler_client(
        description=";".join(
            "{}({})".format(step, ",".join(map(str, set(ids))))
            for step, ids in broken_info.items()
        ),
        service="broken_campaigns",
        status="CRIT" if broken_info else "OK",
    )


@async_time_diff
async def _run_pipeline(steps, campaigns):
    broken_info = {}
    for step in steps:
        step_name = getattr(step, "__name__", step.__class__.__name__)
        with TimeDiff(f"_run_pipeline.{step_name}"):
            pieces = collections.deque()
            pieces.append(campaigns)
            campaigns = []
            while pieces:
                piece = pieces.popleft()
                try:
                    result = step(piece)
                    if inspect.isawaitable(result):
                        result = await result
                    if result is not None:
                        piece = result
                    campaigns += piece
                except StepException as e:
                    troublesome_ids = broken_info.get(step_name, [])
                    troublesome_ids += [
                        x["id"] for x in piece if x["id"] in e.troublesome_ids
                    ]
                    campaigns += [x for x in piece if x["id"] in e.processed_ids]
                    rest = [
                        x
                        for x in piece
                        if x["id"] not in e.troublesome_ids
                        and x["id"] not in e.processed_ids
                    ]

                    if len(rest) < 2:
                        troublesome_ids += [x["id"] for x in rest]
                    else:
                        half = len(rest) // 2
                        pieces.append(rest[:half])
                        pieces.append(rest[half:])

                    broken_info[step_name] = troublesome_ids

    return campaigns, broken_info
