from typing import List

from marshmallow import ValidationError, fields, post_load, pre_dump, validate

from maps_adv.common.protomallow import (
    PbDateTimeField,
    PbEnumField,
    ProtobufSchema,
    with_schemas,
)
from maps_adv.geosmb.booking_yang.proto import orders_pb2
from maps_adv.geosmb.booking_yang.server.lib.domains import OrdersDomain
from maps_adv.geosmb.booking_yang.server.lib.enums import ENUMS_MAP, OrderStatus

__all__ = ["ApiProvider"]


class ActualOrdersInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.ActualOrdersInput

    actual_on = PbDateTimeField(required=True)


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

    permalink = fields.Integer(required=True)
    reservation_datetime = PbDateTimeField(required=True)
    reservation_timezone = fields.String(required=True)
    client_id = fields.Integer()
    passport_uid = fields.Integer()


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

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

    @pre_dump
    def to_dict(self, data: list) -> dict:
        return {"orders": data}


class OrderInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.OrderInput

    permalink = fields.Int(validate=validate.Range(min=0))
    reservation_datetime = PbDateTimeField()
    reservation_timezone = fields.String()
    person_count = fields.Int(validate=validate.Range(min=1))
    customer_name = fields.String(validate=validate.Length(min=1))
    customer_phone = fields.String(validate=validate.Length(min=1))
    comment = fields.String()
    call_agreement_accepted = fields.Bool()
    biz_id = fields.Int(validate=validate.Range(min=0))
    customer_passport_uid = fields.Int(validate=validate.Range(min=1))


class OrderOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.OrderOutput

    processing_time = PbDateTimeField(required=True)


class ListClientOrdersInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.ListClientOrdersInput

    biz_id = fields.Int(validate=validate.Range(min=1), required=True)
    client_id = fields.Int(validate=validate.Range(min=1), required=True)
    datetime_from = PbDateTimeField()
    datetime_to = PbDateTimeField()

    @post_load
    def _validate_time_period(self, data):
        if (
            data.get("datetime_from")
            and data.get("datetime_to")
            and data["datetime_from"] > data["datetime_to"]
        ):
            raise ValidationError("datetime_to should be grater than datetime_from.")


class ClientOrderSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.ClientOrder

    order_id = fields.Int(required=True)
    created_at = PbDateTimeField(required=True)
    reservation_datetime = PbDateTimeField(required=True)
    reservation_timezone = fields.String(required=True)
    person_count = fields.Int(required=True)
    status = PbEnumField(
        enum=OrderStatus,
        pb_enum=orders_pb2.ClientOrder.OrderStatus,
        values_map=ENUMS_MAP["order_status"],
        required=True,
    )


class ClientOrdersSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.ClientOrders

    events_before = fields.Int(required=True)
    events_after = fields.Int(required=True)
    orders = fields.Nested(ClientOrderSchema, many=True)


class ClearOrdersForGdprInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.ClearOrdersForGdprInput

    passport_uid = fields.Integer(required=True)


class ClearOrdersForGdprOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.ClearOrdersForGdprOutput

    cleared_order_ids = fields.List(fields.Integer())

    @pre_dump
    def to_dict(self, data):
        return {"cleared_order_ids": data}


class SearchOrdersForGdprInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.SearchOrdersForGdprInput

    passport_uid = fields.Integer(required=True)


class SearchOrdersForGdprOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = orders_pb2.SearchOrdersForGdprOutput

    orders_exist = fields.Boolean(required=True)

    @pre_dump
    def to_dict(self, data):
        return {"orders_exist": data}


class ApiProvider:
    __slots__ = ("_domain",)

    _domain: OrdersDomain

    def __init__(self, domain: OrdersDomain):
        self._domain = domain

    @with_schemas(input_schema=OrderInputSchema, output_schema=OrderOutputSchema)
    async def make_order(self, **kwargs):
        return {"processing_time": await self._domain.make_order(**kwargs)}

    @with_schemas(
        input_schema=ListClientOrdersInputSchema, output_schema=ClientOrdersSchema
    )
    async def list_client_orders(self, **kwargs):
        return await self._domain.list_client_orders(**kwargs)

    @with_schemas(
        input_schema=ActualOrdersInputSchema, output_schema=ActualOrdersOutputSchema
    )
    async def fetch_actual_orders(self, **kwargs):
        return await self._domain.fetch_actual_orders(**kwargs)

    @with_schemas(
        input_schema=ClearOrdersForGdprInputSchema,
        output_schema=ClearOrdersForGdprOutputSchema,
    )
    async def clear_orders_for_gdpr(self, **kwargs) -> List[int]:
        return await self._domain.clear_orders_for_gdpr(**kwargs)

    @with_schemas(
        input_schema=SearchOrdersForGdprInputSchema,
        output_schema=SearchOrdersForGdprOutputSchema,
    )
    async def search_orders_for_gdpr(self, **kwargs) -> bool:
        return await self._domain.search_orders_for_gdpr(**kwargs)
