import json
from operator import itemgetter
from typing import List, Tuple

from marshmallow import (
    ValidationError,
    fields,
    post_load,
    pre_dump,
    pre_load,
    validate,
    validates,
    validates_schema,
)

from maps_adv.common.protomallow import (
    PbDateTimeField,
    PbEnumField,
    ProtobufSchema,
    with_schemas,
)
from maps_adv.geosmb.doorman.proto import (
    clients_pb2,
    common_pb2,
    events_pb2,
    list_client_segments_pb2,
    segments_pb2,
    statistics_pb2,
)
from maps_adv.geosmb.doorman.server.lib.domain import Domain
from maps_adv.geosmb.doorman.server.lib.enums import (
    CallEvent,
    CallHistoryEvent,
    ClientGender,
    OrderByField,
    OrderDirection,
    OrderEvent,
    SegmentType,
    Source,
)

ENUMS_MAP = {
    "client_gender": [
        (common_pb2.ClientGender.MALE, ClientGender.MALE),
        (common_pb2.ClientGender.FEMALE, ClientGender.FEMALE),
    ],
    "source": [
        (common_pb2.Source.CRM_INTERFACE, Source.CRM_INTERFACE),
        (common_pb2.Source.BOOKING_YANG, Source.BOOKING_YANG),
        (common_pb2.Source.GEOADV_PHONE_CALL, Source.GEOADV_PHONE_CALL),
        (common_pb2.Source.LOYALTY_COUPONS, Source.LOYALTY_COUPONS),
        (common_pb2.Source.BOOKING_WIDGET, Source.BOOKING_WIDGET),
        (common_pb2.Source.BOOKING_REQUEST, Source.BOOKING_REQUEST)
    ],
    "client_search_field": [
        (clients_pb2.ListSuggestClientsInput.PHONE, OrderByField.PHONE),
        (clients_pb2.ListSuggestClientsInput.EMAIL, OrderByField.EMAIL),
    ],
    "segment_type": [
        (segments_pb2.SegmentType.REGULAR, SegmentType.REGULAR),
        (segments_pb2.SegmentType.ACTIVE, SegmentType.ACTIVE),
        (segments_pb2.SegmentType.LOST, SegmentType.LOST),
        (segments_pb2.SegmentType.UNPROCESSED_ORDERS, SegmentType.UNPROCESSED_ORDERS),
        (segments_pb2.SegmentType.NO_ORDERS, SegmentType.NO_ORDERS),
        (
            segments_pb2.SegmentType.MISSED_LAST_CALL,
            SegmentType.MISSED_LAST_CALL,
        ),
        (segments_pb2.SegmentType.SHORT_LAST_CALL, SegmentType.SHORT_LAST_CALL),
    ],
    "order_by_field": [
        (clients_pb2.ClientsOrderBy.OrderField.PHONE, OrderByField.PHONE),
        (clients_pb2.ClientsOrderBy.OrderField.EMAIL, OrderByField.EMAIL),
        (
            clients_pb2.ClientsOrderBy.OrderField.FIRST_AND_LAST_NAME,
            OrderByField.FIRST_AND_LAST_NAME,
        ),
        (clients_pb2.ClientsOrderBy.OrderField.COMMENT, OrderByField.COMMENT),
        (
            clients_pb2.ClientsOrderBy.OrderField.STAT_ORDERS_TOTAL,
            OrderByField.STAT_ORDERS_TOTAL,
        ),
        (
            clients_pb2.ClientsOrderBy.OrderField.STAT_ORDERS_SUCCESSFUL,
            OrderByField.STAT_ORDERS_SUCCESSFUL,
        ),
        (
            clients_pb2.ClientsOrderBy.OrderField.STAT_ORDERS_UNSUCCESSFUL,
            OrderByField.STAT_ORDERS_UNSUCCESSFUL,
        ),
        (
            clients_pb2.ClientsOrderBy.OrderField.STAT_ORDERS_LAST_ORDER_TS,
            OrderByField.STAT_ORDERS_LAST_ORDER_TS,
        ),
    ],
    "order_direction": [
        (clients_pb2.ClientsOrderBy.OrderDirection.ASC, OrderDirection.ASC),
        (clients_pb2.ClientsOrderBy.OrderDirection.DESC, OrderDirection.DESC),
    ],
    "order_event_type": [
        (events_pb2.OrderEventType.CREATED, OrderEvent.CREATED),
        (events_pb2.OrderEventType.REJECTED, OrderEvent.REJECTED),
        (events_pb2.OrderEventType.ACCEPTED, OrderEvent.ACCEPTED),
    ],
    "call_event_type": [
        (events_pb2.CallEvent.INITIATED, CallEvent.INITIATED),
        (events_pb2.CallEvent.FINISHED, CallEvent.FINISHED),
    ],
    "call_history_event_type": [
        (events_pb2.HistoryCallEvent.INITIATED, CallHistoryEvent.INITIATED),
        (events_pb2.HistoryCallEvent.ACCEPTED, CallHistoryEvent.ACCEPTED),
        (events_pb2.HistoryCallEvent.MISSED, CallHistoryEvent.MISSED),
    ],
}


def make_fields_flat(data: dict, fields: List[str]) -> dict:
    for field in fields:
        if field in data:
            data.update(data.pop(field))

    return data


def validate_phone_length(value: int):
    phone_length = len(str(value))
    if phone_length < 3 or phone_length > 16:
        raise ValidationError("Must have at least 3 digits and no more than 16.")


class ClientContactsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClientContacts

    id = fields.Integer(required=True)
    biz_id = fields.Integer(required=True)
    passport_uid = fields.Integer()
    phone = fields.Integer()
    email = fields.String()
    first_name = fields.String()
    last_name = fields.String()
    cleared_for_gdpr = fields.Boolean()


class SourceMetadataSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.SourceMetadata

    source = PbEnumField(
        required=True,
        enum=Source,
        pb_enum=common_pb2.Source,
        values_map=ENUMS_MAP["source"],
    )

    extra = fields.String()
    url = fields.String(validate=validate.Length(min=1))
    device_id = fields.String(validate=validate.Length(min=1, max=64))
    uuid = fields.String(validate=validate.Length(min=1, max=64))

    @validates("extra")
    def validate_extra(self, value: dict):
        try:
            json.loads(value)
        except json.JSONDecodeError:
            raise ValidationError("Invalid json.", field_names=["extra"])

    @post_load
    def to_flat(self, data: dict) -> dict:
        flat_data = {"source": data.pop("source")}

        metadata = {}
        if "extra" in data:
            parsed_extra = json.loads(data.pop("extra"))
            metadata.update(parsed_extra)
        metadata.update(data)

        if metadata:
            flat_data["metadata"] = metadata

        return flat_data


class ClientSetupDataSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClientSetupData

    biz_id = fields.Integer(required=True, validate=validate.Range(min=1))
    metadata = fields.Nested(SourceMetadataSchema, required=True)
    phone = fields.Integer(validate=validate_phone_length)
    email = fields.String(validate=[validate.Length(min=1, max=64), validate.Email()])
    passport_uid = fields.Integer(validate=validate.Range(min=1))

    first_name = fields.String(validate=validate.Length(min=1, max=256))
    last_name = fields.String(validate=validate.Length(min=1, max=256))
    gender = PbEnumField(
        enum=ClientGender,
        pb_enum=common_pb2.ClientGender,
        values_map=ENUMS_MAP["client_gender"],
    )
    comment = fields.String(validate=validate.Length(min=1))
    initiator_id = fields.Integer()

    @post_load
    def normalize_data(self, data: dict) -> dict:
        data = self.validate_requirement(data)
        return self.to_flat(data)

    @staticmethod
    def validate_requirement(data: dict) -> dict:
        id_fields = ["phone", "email", "passport_uid"]
        if not any([field in data for field in id_fields]):
            raise ValidationError(
                "At least one of identity fields must be set.", field_names=id_fields
            )

        return data

    @staticmethod
    def to_flat(data: dict) -> dict:
        return make_fields_flat(data, ["metadata"])


class PaginationSchema(ProtobufSchema):
    class Meta:
        pb_message_class = common_pb2.Pagination

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


class ClientUpdateDataSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClientUpdateData

    client_id = fields.Integer(
        required=True, load_from="id", validate=validate.Range(min=1)
    )
    data = fields.Nested(ClientSetupDataSchema, required=True)

    @post_load
    def to_flat(self, data: dict):
        data.update(data.pop("data"))


class OrderStatisticsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = statistics_pb2.OrderStatistics

    total = fields.Integer(required=True)
    successful = fields.Integer(required=True)
    unsuccessful = fields.Integer(required=True)
    last_order_timestamp = PbDateTimeField()


class ClientStatisticsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = statistics_pb2.ClientStatistics

    orders = fields.Nested(OrderStatisticsSchema, required=True)


class ClientDataSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClientData

    id = fields.Integer(required=True)
    biz_id = fields.Integer(required=True)
    phone = fields.Integer()
    email = fields.String()
    passport_uid = fields.Integer()
    first_name = fields.String()
    last_name = fields.String()
    gender = PbEnumField(
        enum=ClientGender,
        pb_enum=common_pb2.ClientGender,
        values_map=ENUMS_MAP["client_gender"],
    )
    comment = fields.String()
    segments = fields.List(
        PbEnumField(
            enum=SegmentType,
            pb_enum=segments_pb2.SegmentType,
            values_map=ENUMS_MAP["segment_type"],
        )
    )
    statistics = fields.Nested(ClientStatisticsSchema)
    source = PbEnumField(
        required=True,
        enum=Source,
        pb_enum=common_pb2.Source,
        values_map=ENUMS_MAP["source"],
    )
    registration_timestamp = PbDateTimeField()
    cleared_for_gdpr = fields.Boolean()
    labels = fields.List(fields.String())


class ClientRetrieveInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClientRetrieveInput

    biz_id = fields.Integer(required=True, validate=validate.Range(min=1))
    client_id = fields.Integer(
        load_from="id", validate=validate.Range(min=1), required=True
    )


class ClientsOrderBySchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClientsOrderBy

    field = PbEnumField(
        required=True,
        enum=OrderByField,
        pb_enum=clients_pb2.ClientsOrderBy.OrderField,
        values_map=ENUMS_MAP["order_by_field"],
    )
    direction = PbEnumField(
        required=True,
        enum=OrderDirection,
        pb_enum=clients_pb2.ClientsOrderBy.OrderDirection,
        values_map=ENUMS_MAP["order_direction"],
    )


class ClientsListInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClientsListInput

    biz_id = fields.Integer(required=True, validate=validate.Range(min=1))
    search_string = fields.String(validate=validate.Length(min=3, max=512))
    pagination = fields.Nested(PaginationSchema, required=True)
    segment_type = PbEnumField(
        enum=SegmentType,
        pb_enum=segments_pb2.SegmentType,
        values_map=ENUMS_MAP["segment_type"],
        load_from="segment",
    )
    label = fields.String(validate=validate.Length(min=1, max=256))
    client_ids = fields.List(fields.Integer(validate=validate.Range(min=1)))
    order_by = fields.Nested(ClientsOrderBySchema)

    @post_load
    def unpack_pagination(self, data: dict) -> dict:
        limit, offset = itemgetter("limit", "offset")(data.pop("pagination"))
        data.update(limit=limit, offset=offset)
        return data

    @post_load
    def unpack_ordering(self, data: dict) -> dict:
        if data.get("order_by"):
            field, direction = itemgetter("field", "direction")(data.pop("order_by"))
            data.update(order_by_field=field, order_direction=direction)
        return data


class ClientsListOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClientsListOutput

    clients = fields.Nested(ClientDataSchema, many=True)
    total_count = fields.Integer(required=True)

    @pre_dump
    def to_dict(self, data):
        total_count, clients = data
        return {"total_count": total_count, "clients": clients}


class OrderEventSchema(ProtobufSchema):
    class Meta:
        pb_message_class = events_pb2.OrderEvent

    event_type = PbEnumField(
        required=True,
        enum=OrderEvent,
        pb_enum=events_pb2.OrderEventType,
        values_map=ENUMS_MAP["order_event_type"],
        load_from="type",
    )
    order_id = fields.Integer(required=True, validate=validate.Range(min=1))


class CallEventSchema(ProtobufSchema):
    class Meta:
        pb_message_class = events_pb2.CallEvent

    event_type = PbEnumField(
        required=True,
        enum=CallEvent,
        pb_enum=events_pb2.CallEvent.EventType,
        values_map=ENUMS_MAP["call_event_type"],
        load_from="type",
    )
    session_id = fields.Integer(required=True)
    event_value = fields.String(required=False, load_from="value")


class AddEventInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = events_pb2.AddEventInput

    biz_id = fields.Integer(required=True, validate=validate.Range(min=1))
    client_id = fields.Integer(required=True, validate=validate.Range(min=1))
    source = PbEnumField(
        required=True,
        enum=Source,
        pb_enum=common_pb2.Source,
        values_map=ENUMS_MAP["source"],
    )
    event_timestamp = PbDateTimeField(required=True, load_from="timestamp")
    order_event = fields.Nested(OrderEventSchema)
    call_event = fields.Nested(CallEventSchema)


class ListSegmentsInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = segments_pb2.ListSegmentsInput

    biz_id = fields.Integer(required=True, validate=validate.Range(min=1))


class SegmentSchema(ProtobufSchema):
    class Meta:
        pb_message_class = segments_pb2.Segment

    segment_type = PbEnumField(
        required=True,
        enum=SegmentType,
        pb_enum=segments_pb2.SegmentType,
        values_map=ENUMS_MAP["segment_type"],
        dump_to="type",
    )
    current_size = fields.Integer(required=True)
    previous_size = fields.Integer(required=True)


class LabelSchema(ProtobufSchema):
    class Meta:
        pb_message_class = segments_pb2.Label

    name = fields.String(required=True)
    size = fields.Integer(required=True)


class ListSegmentsOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = segments_pb2.ListSegmentsOutput

    total_clients_current = fields.Integer(required=True)
    total_clients_previous = fields.Integer(required=True)
    segments = fields.Nested(SegmentSchema, many=True)
    labels = fields.Nested(LabelSchema, many=True)


class SegmentStatisticsInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = segments_pb2.SegmentStatisticsInput

    biz_id = fields.Integer(required=True, validate=validate.Range(min=1))
    segment_type = PbEnumField(
        required=False,
        missing=None,
        enum=SegmentType,
        pb_enum=segments_pb2.SegmentType,
        values_map=ENUMS_MAP["segment_type"],
    )


class SegmentStatisticsOnDateSchema(ProtobufSchema):
    class Meta:
        pb_message_class = segments_pb2.SegmentStatisticsOnDate

    timestamp = PbDateTimeField(required=True)
    size = fields.Integer(required=True)


class SegmentStatisticsOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = segments_pb2.SegmentStatisticsOutput

    statistics = fields.Nested(SegmentStatisticsOnDateSchema, many=True)

    @pre_dump
    def _expand_statistics(self, data):
        data["statistics"] = [{"timestamp": k, "size": v} for k, v in data.items()]


class ListSuggestClientsInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ListSuggestClientsInput

    biz_id = fields.Integer(required=True, validate=validate.Range(min=1))
    search_field = PbEnumField(
        required=True,
        enum=OrderByField,
        pb_enum=clients_pb2.ListSuggestClientsInput.ClientSearchField,
        values_map=ENUMS_MAP["client_search_field"],
    )
    search_string = fields.String(
        required=True, validate=validate.Length(min=3, max=64)
    )
    limit = fields.Integer(required=True, validate=validate.Range(min=1), missing=5)


class ClientContactsListSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClientContactsList

    clients = fields.List(fields.Nested(ClientContactsSchema), required=True)

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


class ContactsListInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ListContactsInput

    client_ids = fields.List(fields.Integer())

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


class ClientsListBySegmentInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClientsListBySegmentInput

    biz_id = fields.Integer(required=True)
    segment = PbEnumField(
        enum=SegmentType,
        pb_enum=segments_pb2.SegmentType,
        values_map=ENUMS_MAP["segment_type"],
    )
    label = fields.String(validate=validate.Length(min=1, max=256))

    @validates_schema(skip_on_field_errors=True)
    def _validate_required_fields(self, data: dict) -> None:
        if not any([data.get("segment"), data.get("label")]):
            raise ValidationError("At least one of segment or label must be specified.")


class BusinessSegmentsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = list_client_segments_pb2.BusinessSegments

    client_id = fields.Integer(required=True)
    biz_id = fields.Integer(required=True)
    segments = fields.List(
        PbEnumField(
            required=True,
            enum=SegmentType,
            pb_enum=segments_pb2.SegmentType,
            values_map=ENUMS_MAP["segment_type"],
        )
    )
    labels = fields.List(fields.String())


class ListClientSegmentsOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = list_client_segments_pb2.ListClientSegmentsOutput

    biz_segments = fields.Nested(BusinessSegmentsSchema, many=True)

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


class ListClientSegmentsInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = list_client_segments_pb2.ListClientSegmentsInput

    passport_uid = fields.Integer(required=False, validate=validate.Range(min=1))
    phone = fields.Integer(required=False, validate=validate.Range(min=1))
    email = fields.String(required=False, validate=validate.Length(min=1))
    biz_id = fields.Integer(required=False, validate=validate.Range(min=1))

    @pre_load
    def validate_id_fields(self, data):
        if not any(data.values()):
            raise ValidationError(
                "At least one id field should be listed: passport_uid, phone or email"
            )

        return data


class RetrieveClientCallsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = events_pb2.RetrieveClientCalls

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


class AcceptedDetailsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = events_pb2.HistoryCallEvent.AcceptedDetails

    talk_duration = fields.Integer(required=True)


class MissedDetailsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = events_pb2.HistoryCallEvent.MissedDetails

    await_duration = fields.Integer(required=True)


class HistoryCallEventSchema(ProtobufSchema):
    class Meta:
        pb_message_class = events_pb2.HistoryCallEvent

    timestamp = PbDateTimeField(required=False)
    source = PbEnumField(
        required=True,
        enum=Source,
        pb_enum=common_pb2.Source,
        values_map=ENUMS_MAP["source"],
    )
    type = PbEnumField(
        required=True,
        enum=CallEvent,
        pb_enum=events_pb2.HistoryCallEvent.EventType,
        values_map=ENUMS_MAP["call_history_event_type"],
    )
    accepted_details = fields.Nested(AcceptedDetailsSchema)
    missed_details = fields.Nested(MissedDetailsSchema)


class ClientHistorySchema(ProtobufSchema):
    class Meta:
        pb_message_class = events_pb2.ClientHistory

    calls = fields.List(fields.Nested(HistoryCallEventSchema), required=True)
    events_before = fields.Integer(required=True)
    events_after = fields.Integer(required=True)


class ClearClientsForGdprInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClearClientsForGdprInput

    passport_uid = fields.Integer(required=True)


class RemovedClientSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClearClientsForGdprOutput.ClearedClient

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


class ClearClientsForGdprOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.ClearClientsForGdprOutput

    cleared_clients = fields.List(fields.Nested(RemovedClientSchema), required=True)

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


class SearchClientsForGdprInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.SearchClientsForGdprInput

    passport_uid = fields.Integer(required=True)


class SearchClientsForGdprOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.SearchClientsForGdprOutput

    clients_exist = fields.Boolean(required=True)

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


class BulkClientSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.BulkCreateClientsInput.BulkClient

    first_name = fields.String(validate=validate.Length(min=1, max=256), missing=None)
    last_name = fields.String(validate=validate.Length(min=1, max=256), missing=None)
    phone = fields.Integer(validate=validate_phone_length, missing=None)
    email = fields.String(
        validate=[validate.Length(min=1, max=64), validate.Email()], missing=None
    )
    comment = fields.String(validate=validate.Length(min=1), missing=None)

    @validates_schema(skip_on_field_errors=True)
    def _validate(self, data: dict) -> None:
        if all([not data.get(field) for field in ["phone", "email"]]):
            raise ValidationError(
                "At least one of identity fields [phone, email] must be set."
            )

    @post_load
    def _normalize(self, data: dict) -> dict:
        if data["phone"]:
            data["phone"] = str(data["phone"])

        return data


class BulkCreateClientsInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.BulkCreateClientsInput

    biz_id = fields.Integer(required=True, validate=validate.Range(min=1))
    source = PbEnumField(
        required=True,
        enum=Source,
        pb_enum=common_pb2.Source,
        values_map=ENUMS_MAP["source"],
    )
    label = fields.String(validate=validate.Length(min=1, max=256))
    clients = fields.Nested(BulkClientSchema, many=True)


class BulkCreateClientsOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = clients_pb2.BulkCreateClientsOutput

    total_created = fields.Integer()
    total_merged = fields.Integer()


class ApiProvider:
    __slots__ = ["_domain"]

    _domain: Domain

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

    @with_schemas(input_schema=ClientSetupDataSchema, output_schema=ClientDataSchema)
    async def create_client(self, **kwargs) -> dict:
        return await self._domain.create_client(**kwargs)

    @with_schemas(
        input_schema=BulkCreateClientsInputSchema,
        output_schema=BulkCreateClientsOutputSchema,
    )
    async def create_clients(self, **kwargs) -> dict:
        return await self._domain.create_clients(**kwargs)

    @with_schemas(
        input_schema=ClientRetrieveInputSchema, output_schema=ClientDataSchema
    )
    async def retrieve_client(self, **kwargs) -> dict:
        return await self._domain.retrieve_client(**kwargs)

    @with_schemas(
        input_schema=ClientsListInputSchema, output_schema=ClientsListOutputSchema
    )
    async def list_clients(self, **kwargs) -> Tuple[int, List[dict]]:
        return await self._domain.list_clients(**kwargs)

    @with_schemas(
        input_schema=ContactsListInputSchema,
        output_schema=ClientContactsListSchema,
    )
    async def list_contacts(self, **kwargs) -> List[dict]:
        return await self._domain.list_contacts(**kwargs)

    @with_schemas(
        input_schema=ListSuggestClientsInputSchema,
        output_schema=ClientContactsListSchema,
    )
    async def list_suggest_clients(self, **kwargs) -> List[dict]:
        return await self._domain.list_suggest_clients(**kwargs)

    @with_schemas(input_schema=ClientUpdateDataSchema, output_schema=ClientDataSchema)
    async def update_client(self, **kwargs) -> dict:
        return await self._domain.update_client(**kwargs)

    @with_schemas(input_schema=AddEventInputSchema)
    async def add_event(self, **kwargs) -> dict:
        return await self._domain.add_event(**kwargs)

    @with_schemas(
        input_schema=ListSegmentsInputSchema, output_schema=ListSegmentsOutputSchema
    )
    async def list_segments(self, **kwargs) -> dict:
        return await self._domain.list_segments(**kwargs)

    @with_schemas(
        input_schema=SegmentStatisticsInputSchema,
        output_schema=SegmentStatisticsOutputSchema,
    )
    async def segment_statistics(self, **kwargs) -> dict:
        return await self._domain.segment_statistics(**kwargs)

    @with_schemas(
        input_schema=ClientsListBySegmentInputSchema,
        output_schema=ClientContactsListSchema,
    )
    async def list_clients_by_segment(self, **kwargs) -> List[dict]:
        return await self._domain.list_clients_by_segment(**kwargs)

    @with_schemas(
        input_schema=ListClientSegmentsInputSchema,
        output_schema=ListClientSegmentsOutputSchema,
    )
    async def list_client_segments(self, **kwargs) -> List[dict]:
        return await self._domain.list_client_segments(**kwargs)

    @with_schemas(
        input_schema=RetrieveClientCallsSchema, output_schema=ClientHistorySchema
    )
    async def list_client_events(self, **kwargs) -> dict:
        return await self._domain.list_client_events(**kwargs)

    @with_schemas(
        input_schema=ClearClientsForGdprInputSchema,
        output_schema=ClearClientsForGdprOutputSchema,
    )
    async def clear_clients_for_gdpr(self, **kwargs) -> List[dict]:
        return await self._domain.clear_clients_for_gdpr(**kwargs)

    @with_schemas(
        input_schema=SearchClientsForGdprInputSchema,
        output_schema=SearchClientsForGdprOutputSchema,
    )
    async def search_clients_for_gdpr(self, **kwargs) -> bool:
        return await self._domain.search_clients_for_gdpr(**kwargs)
