from marshmallow import fields, post_load

from maps_adv.common.protomallow import PbDateTimeField, PbEnumField, ProtobufSchema
from maps_adv.geosmb.clients.market.proto import (
    categories_pb2,
    common_pb2,
    orders_pb2,
    services_pb2,
)

from .enums import (
    ClientAction,
    FilterServiceCategoriesOrderingFields,
    FilterServiceOrderingFields,
    OrderType,
    PriceType,
    ServiceStatus,
)
from .exceptions import (
    MarketIntBadProto,
    MarketIntConflict,
    MarketIntNotFound,
    MarketIntUnknownError,
)

MAP_PROTO_ERROR_TO_EXCEPTION = {
    common_pb2.Error.ErrorCode.UNKNOWN: lambda e: MarketIntUnknownError(e.message),
    common_pb2.Error.ErrorCode.NOT_FOUND: lambda e: MarketIntNotFound(e.message),
    common_pb2.Error.ErrorCode.BAD_PROTO: lambda e: MarketIntBadProto(e.message),
    common_pb2.Error.ErrorCode.CONFLICT: lambda e: MarketIntConflict(e.message),
}

PROTO_TO_ENUM_MAP = {
    "order_type": [
        (common_pb2.OrderType.ASC, OrderType.ASC),
        (common_pb2.OrderType.DESC, OrderType.DESC),
    ],
    "service_status": [
        (services_pb2.ServiceStatus.DRAFT, ServiceStatus.DRAFT),
        (services_pb2.ServiceStatus.PUBLISHED, ServiceStatus.PUBLISHED),
        (services_pb2.ServiceStatus.DELETED, ServiceStatus.DELETED),
    ],
    "filter_service_ordering_fields": [
        (
            services_pb2.FilterServiceOrderingFields.NAME,
            FilterServiceOrderingFields.NAME,
        ),
        (
            services_pb2.FilterServiceOrderingFields.STATUS,
            FilterServiceOrderingFields.STATUS,
        ),
        (
            services_pb2.FilterServiceOrderingFields.UPDATED_AT,
            FilterServiceOrderingFields.UPDATED_AT,
        ),
        (
            services_pb2.FilterServiceOrderingFields.PRICE_MIN,
            FilterServiceOrderingFields.PRICE_MIN,
        ),
        (
            services_pb2.FilterServiceOrderingFields.DURATION_MINUTES,
            FilterServiceOrderingFields.DURATION_MINUTES,
        ),
    ],
    "filter_service_categories_ordering_fields": [
        (
            categories_pb2.FilterServiceCategoriesOrderingFields.NAME,
            FilterServiceCategoriesOrderingFields.NAME,
        )
    ],
    "price_type": [
        (services_pb2.PriceType.UNKNOWN, PriceType.UNKNOWN),
        (services_pb2.PriceType.FIXED, PriceType.FIXED),
        (services_pb2.PriceType.FROM, PriceType.FROM),
    ],
    "client_action": [
        (
            services_pb2.ClientAction.BOOKING_WITH_EMPLOYEE,
            ClientAction.BOOKING_WITH_EMPLOYEE,
        ),
        (
            services_pb2.ClientAction.BOOKING_WITHOUT_EMPLOYEE,
            ClientAction.BOOKING_WITHOUT_EMPLOYEE,
        ),
        (services_pb2.ClientAction.REQUEST, ClientAction.REQUEST),
        (services_pb2.ClientAction.CALL, ClientAction.CALL),
        (services_pb2.ClientAction.LINK, ClientAction.LINK),
    ],
}


class FilterServicesFilteringParametersSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.FilterServicesFilteringParameters

    query = fields.String()
    category_ids = fields.List(fields.Integer())
    statuses = fields.List(
        PbEnumField(
            enum=ServiceStatus,
            pb_enum=services_pb2.ServiceStatus,
            values_map=PROTO_TO_ENUM_MAP["service_status"],
        ),
    )
    include_without_categories = fields.Bool()


class FilterServiceOrderingParametersSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.FilterServiceOrderingParameters

    type = PbEnumField(
        enum=OrderType,
        pb_enum=common_pb2.OrderType,
        values_map=PROTO_TO_ENUM_MAP["order_type"],
        required=True,
    )
    field = PbEnumField(
        enum=FilterServiceOrderingFields,
        pb_enum=services_pb2.FilterServiceOrderingFields,
        values_map=PROTO_TO_ENUM_MAP["filter_service_ordering_fields"],
        required=True,
    )


class PagingInputParametersSchema(ProtobufSchema):
    class Meta:
        pb_message_class = common_pb2.PagingInputParameters

    offset = fields.Integer(required=True)
    limit = fields.Integer(required=True)


class PagingOutputParametersSchema(ProtobufSchema):
    class Meta:
        pb_message_class = common_pb2.PagingOutputParameters

    offset = fields.Integer(required=True)
    limit = fields.Integer(required=True)
    total = fields.Integer(required=True)


class FilterServicesParametersSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.FilterServicesParameters

    biz_id = fields.Integer(required=True)
    paging = fields.Nested(PagingInputParametersSchema)
    ordering = fields.Nested(FilterServiceOrderingParametersSchema, many=True)
    filtering = fields.Nested(FilterServicesFilteringParametersSchema)


class MoneySchema(ProtobufSchema):
    class Meta:
        pb_message_class = common_pb2.Money

    value = fields.String(required=True)
    currency = fields.String(required=True)


class ServiceSchedulePricesSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.ServiceSchedulePrices

    schedule_id = fields.Integer(required=True)


class ServiceLevelPriceSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.ServiceLevelPrice

    level = fields.Integer(required=True)


class ServicePriceSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.ServicePrice

    service_id = fields.Integer(required=True)
    price = fields.Nested(MoneySchema, required=True)
    duration_minutes = fields.Integer()
    schedule = fields.Nested(ServiceSchedulePricesSchema)
    price_level = fields.Nested(ServiceLevelPriceSchema)


class ClientActionSettingsLinkSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.ClientActionSettingsLink

    link = fields.String(required=True)
    button_text = fields.String(required=True)


class ClientActionSettingsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.ClientActionSettings

    link = fields.Nested(ClientActionSettingsLinkSchema)


class ServiceListItemSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.ServiceListItem

    item_id = fields.Integer(required=True, load_from="id")
    biz_id = fields.Integer(required=True)
    name = fields.String(required=True)
    status = PbEnumField(
        enum=ServiceStatus,
        pb_enum=services_pb2.ServiceStatus,
        values_map=PROTO_TO_ENUM_MAP["service_status"],
        required=True,
    )
    category_ids = fields.List(fields.Integer())
    min_price = fields.Nested(MoneySchema)
    duration_minutes = fields.Integer()
    main_image_url_template = fields.String()
    updated_at = fields.String(required=True)
    service_prices = fields.Nested(ServicePriceSchema, many=True)
    description = fields.String()
    price_type = PbEnumField(
        enum=PriceType,
        pb_enum=services_pb2.PriceType,
        values_map=PROTO_TO_ENUM_MAP["price_type"],
        required=True,
    )
    client_action = PbEnumField(
        enum=ClientAction,
        pb_enum=services_pb2.ClientAction,
        values_map=PROTO_TO_ENUM_MAP["client_action"],
    )
    client_action_settings = fields.Nested(ClientActionSettingsSchema)

    @post_load
    def trim_min_price(self, data):
        if "min_price" in data and data["min_price"]["value"] == "":
            data.pop("min_price")
        return data


class ServiceListSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.ServiceList

    paging = fields.Nested(PagingOutputParametersSchema, required=True)
    ordering = fields.Nested(FilterServiceOrderingParametersSchema, many=True)
    items = fields.Nested(ServiceListItemSchema, many=True)


class ServiceCategorySchema(ProtobufSchema):
    class Meta:
        pb_message_class = categories_pb2.ServiceCategory

    category_id = fields.Integer(load_from="id", required=True)
    biz_id = fields.Integer(required=True)
    name = fields.String(required=True)


class FilterServiceCategoriesOrderingParametersSchema(ProtobufSchema):
    class Meta:
        pb_message_class = categories_pb2.FilterServiceCategoriesOrderingParameters

    type = PbEnumField(
        enum=OrderType,
        pb_enum=common_pb2.OrderType,
        values_map=PROTO_TO_ENUM_MAP["order_type"],
        required=True,
    )
    field = PbEnumField(
        enum=FilterServiceCategoriesOrderingFields,
        pb_enum=categories_pb2.FilterServiceCategoriesOrderingFields,
        values_map=PROTO_TO_ENUM_MAP["filter_service_categories_ordering_fields"],
        required=True,
    )


class FilterServiceCategoriesParametersSchema(ProtobufSchema):
    class Meta:
        pb_message_class = categories_pb2.FilterServiceCategoriesParameters

    biz_id = fields.Integer(required=True)
    paging = fields.Nested(PagingInputParametersSchema)
    query = fields.String()
    ordering = fields.Nested(FilterServiceCategoriesOrderingParametersSchema, many=True)


class ServiceCategoryListSchema(ProtobufSchema):
    class Meta:
        pb_message_class = categories_pb2.ServiceCategoryList

    paging = fields.Nested(PagingOutputParametersSchema)
    ordering = fields.Nested(FilterServiceCategoriesOrderingParametersSchema, many=True)
    items = fields.Nested(ServiceCategorySchema, many=True)


class ActualOrderItemSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.ActualOrderItem

    client_id = fields.Integer(required=True)
    biz_id = fields.Integer(required=True)
    reservation_datetime = PbDateTimeField(required=True)


class ActualOrdersOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.ActualOrdersOutput

    orders = fields.Nested(ActualOrderItemSchema, many=True)
