import json
import logging

from aiohttp import web
from marshmallow import ValidationError

from maps_adv.adv_store.api.proto.error_pb2 import Error
from maps_adv.adv_store.v2.lib.data_managers.exceptions import CampaignNotFound
from maps_adv.adv_store.v2.lib.domains.campaigns import (
    BillingTypeChange,
    BudgetIsTooLow,
    CampaignsNotFound,
    OrderIdChange,
    PeriodIntersectingDiscounts,
    ShowingPeriodEndsInThePast,
    ShowingPeriodStartLaterThanEnd,
    WrongCampaignTypeForPlatform,
    WrongCreativesForCampaignType,
    WrongPlacingForCampaignType,
    InvalidBilling,
    InvalidCustomPageId,
    PaidTillIsTooSmall,
    InconsistentCreatives,
)
from maps_adv.adv_store.v2.lib.domains.moderation import (
    CampaignNotInReview,
    ReviewCommentEmpty,
)

logger = logging.getLogger(__name__)


@web.middleware
async def handle_validation_exception(request, handler):
    try:
        return await handler(request)
    except ValidationError as exc:
        logger.error("Serialization error: %s", exc.normalized_messages())
        if "pb_error_code" in exc.kwargs:
            error = Error(code=exc.kwargs["pb_error_code"])
        else:
            error = Error(
                code=Error.INVALID_INPUT_DATA, description=json.dumps(exc.messages)
            )
        return web.Response(status=400, body=error.SerializeToString())


_exceptions_to_code_map = {
    BillingTypeChange: Error.BILLING_TYPED_CHANGED,
    BudgetIsTooLow: Error.CAMPAIGN_BUDGET_IS_TOO_LOW,
    CampaignNotInReview: Error.CAMPAIGN_NOT_IN_REVIEW,
    OrderIdChange: Error.ORDER_ID_CHANGED,
    PeriodIntersectingDiscounts: Error.DISCOUNTS_WITH_INTERSECTING_DATES,
    ReviewCommentEmpty: Error.REVIEW_COMMENT_EMPTY,
    ShowingPeriodEndsInThePast: Error.SHOWING_PERIOD_ENDS_IN_THE_PAST,
    ShowingPeriodStartLaterThanEnd: Error.SHOWING_PERIOD_START_LATER_THAN_END,
    WrongCampaignTypeForPlatform: Error.INVALID_CAMPAIGN_TYPE_FOR_PLATFORM,
    WrongCreativesForCampaignType: Error.WRONG_SET_OF_CREATIVES_FOR_THE_CAMPAIGN_TYPE,
    WrongPlacingForCampaignType: Error.INVALID_PLACING_FOR_CAMPAIGN_TYPE,
    InvalidBilling: Error.INVALID_BILLING,
    InvalidCustomPageId: Error.INVALID_CUSTOM_PAGE_ID,
    PaidTillIsTooSmall: Error.PAID_TILL_IS_TOO_SMALL,
    InconsistentCreatives: Error.INCONSISTENT_CREATIVES,
}
_domain_exceptions = tuple(_exceptions_to_code_map.keys())


@web.middleware
async def handle_domain_exception(request, handler):
    try:
        return await handler(request)
    except _domain_exceptions as exc:
        logger.exception(exc)
        error = Error(code=_exceptions_to_code_map[type(exc)])
        if exc.args:
            error.description = exc.args[0]
        return web.Response(status=400, body=error.SerializeToString())


@web.middleware
async def handle_object_not_found(request, handler):
    try:
        return await handler(request)
    except CampaignNotFound as exc:
        logger.exception(exc)
        error = Error(code=Error.CAMPAIGN_NOT_FOUND)
        return web.Response(status=404, body=error.SerializeToString())
    except CampaignsNotFound as exc:
        logger.exception(exc)
        error = Error(
            code=Error.CAMPAIGN_NOT_FOUND,
            description=", ".join(map(str, sorted(exc.not_found_campaigns))),
        )
        return web.Response(status=404, body=error.SerializeToString())
