from datetime import datetime, timezone
from typing import List

import aiohttp
from tenacity import RetryError

from maps_adv.adv_store.api.proto.error_pb2 import Error
from maps_adv.adv_store.api.schemas import (
    CampaignDataForMonitoringsListSchema,
    CampaignEventDataListSchema,
    CampaignForBudgetAnalysisListSchema,
    CampaignForChargerListSchema,
    CampaignForChargerCpaListSchema,
    CampaignForChargerFixListSchema,
    CampaignForExportListSchema,
    CampaignIdListSchema,
    CampaignsToStopListSchema,
    CampaignPaidTillChangeInputSchema,
)
from maps_adv.adv_store.api.schemas.enums import CampaignEventTypeEnum
from maps_adv.common.client import Client as BaseClient
from .exceptions import (
    CampaignsNotFound,
    UnexpectedNaiveDatetime,
    UnknownResponse,
)


class Client(BaseClient):
    async def list_active_cpm_campaigns(self, on_datetime: datetime) -> List[dict]:
        params = {"active_at": int(on_datetime.timestamp())}

        try:
            response_body = await self._retryer.call(
                self._request,
                "GET",
                "/v2/campaigns/charger/cpm/",
                params=params,
                expected_status=200,
            )
        except RetryError as exc:
            exc.reraise()

        return CampaignForChargerListSchema().from_bytes(response_body)

    async def list_active_cpa_campaigns(self, on_datetime: datetime) -> List[dict]:
        params = {"active_at": int(on_datetime.timestamp())}

        try:
            response_body = await self._retryer.call(
                self._request,
                "GET",
                "/v2/campaigns/charger/cpa/",
                params=params,
                expected_status=200,
            )
        except RetryError as exc:
            exc.reraise()

        return CampaignForChargerCpaListSchema().from_bytes(response_body)

    async def list_active_fix_campaigns(self, on_datetime: datetime) -> List[dict]:
        params = {"active_at": int(on_datetime.timestamp())}

        try:
            response_body = await self._retryer.call(
                self._request,
                "GET",
                "/v2/campaigns/charger/fix/",
                params=params,
                expected_status=200,
            )
        except RetryError as exc:
            exc.reraise()

        return CampaignForChargerFixListSchema().from_bytes(response_body)

    async def list_campaigns_for_export(self) -> List[dict]:
        try:
            response_body = await self._retryer.call(
                self._request,
                "GET",
                "/v2/campaigns/export/",
                expected_status=200,
            )
        except RetryError as exc:
            exc.reraise()

        return CampaignForExportListSchema().from_bytes(response_body)

    async def stop_campaigns(
        self, *, processed_at: datetime, campaigns_to_stop: dict
    ) -> None:
        if processed_at.tzinfo is None:
            raise UnexpectedNaiveDatetime(processed_at)

        try:
            await self._retryer.call(
                self._request,
                "PUT",
                "/v2/campaigns/stop/",
                data=CampaignsToStopListSchema().to_bytes(
                    {"campaigns": campaigns_to_stop, "processed_at": processed_at}
                ),
                expected_status=200,
            )
        except RetryError as exc:
            exc.reraise()

    @staticmethod
    async def _check_response(response: aiohttp.ClientResponse):
        if response.status == 404:
            response_body = await response.read()
            error = Error.FromString(response_body)

            raise CampaignsNotFound(error.description)

        raise UnknownResponse(response.status)

    async def create_campaign_events(self, events: List[dict]) -> None:
        try:
            await self._retryer.call(
                self._request,
                "POST",
                "/v2/create-campaign-events/",
                data=CampaignEventDataListSchema().to_bytes({"events": events}),
                expected_status=200,
            )
        except RetryError as exc:
            exc.reraise()

    async def create_campaign_not_spending_budget_events(
        self, campaign_ids: List[int]
    ) -> None:
        timestamp = datetime.now(tz=timezone.utc)
        await self.create_campaign_events(
            list(
                map(
                    lambda campaign_id: {
                        "timestamp": timestamp,
                        "campaign_id": campaign_id,
                        "event_type": CampaignEventTypeEnum.NOT_SPENDING_BUDGET,
                    },
                    campaign_ids,
                )
            )
        )

    async def list_campaigns_for_budget_analysis(self) -> List[dict]:

        try:
            response_body = await self._retryer.call(
                self._request,
                "GET",
                "/v2/campaigns/budget-analysis/",
                expected_status=200,
            )
        except RetryError as exc:
            exc.reraise()

        return CampaignForBudgetAnalysisListSchema().from_bytes(response_body)

    async def retrieve_campaign_data_for_monitorings(
        self, campaign_ids: List[int]
    ) -> List[dict]:
        try:
            response_body = await self._retryer.call(
                self._request,
                "POST",
                "/v2/campaigns/monitoring-info/",
                data=CampaignIdListSchema().to_bytes(campaign_ids),
                expected_status=200,
            )
        except RetryError as exc:
            exc.reraise()

        return CampaignDataForMonitoringsListSchema().from_bytes(response_body)

    async def set_paid_till(self, campaign_id, paid_till):
        if paid_till.tzinfo is None:
            raise UnexpectedNaiveDatetime(paid_till)

        try:
            await self._retryer.call(
                self._request,
                "PUT",
                f"/v2/campaigns/{campaign_id}/paid-till/",
                data=CampaignPaidTillChangeInputSchema().to_bytes(
                    {"paid_till": paid_till}
                ),
            )
        except RetryError as exc:
            exc.reraise()
