from marshmallow import Schema, fields, post_load, validate

from maps_adv.common.protomallow import ProtobufSchema, PbEnumField, with_schemas
from maps_adv.geosmb.tuner.proto import settings_pb2, permissions_pb2, telegram_pb2
from maps_adv.geosmb.tuner.server.lib.domain import Domain
from maps_adv.geosmb.tuner.server.lib.enums import PermissionFlag


PROTO_TO_ENUM_MAP = {
    "permission_flags": [
        (permissions_pb2.PermissionFlag.READ_REQUESTS, PermissionFlag.READ_REQUESTS),
    ]
}


class EmailNotificationsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.EmailNotifications

    order_created = fields.Bool(required=True)
    order_cancelled = fields.Bool(required=True)
    order_changed = fields.Bool(required=True)
    certificate_notifications = fields.Bool(required=True)
    request_created = fields.Bool(required=False)


class SmsNotificationsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.SmsNotifications

    request_created = fields.Bool(required=True)


class OnlineBookingSettingsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.OnlineBookingSettings

    export_to_ya_services = fields.Bool(required=True)


class BookingSchema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.Booking

    enabled = fields.Bool(required=True)
    slot_interval = fields.Integer(required=True, validate=[validate.Range(min=1)])
    online_settings = fields.Nested(OnlineBookingSettingsSchema)


class RequestsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.Requests

    enabled = fields.Bool(required=True)
    button_text = fields.String(required=True)


class SettingsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.Settings

    emails = fields.List(
        fields.String(validate=[validate.Length(min=1)]), required=True
    )
    phone = fields.Integer(validate=[validate.Range(min=1)], missing=None)
    notifications = fields.Nested(EmailNotificationsSchema, required=True)
    sms_notifications = fields.Nested(SmsNotificationsSchema)
    booking = fields.Nested(BookingSchema)
    requests = fields.Nested(RequestsSchema)


class ContactsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.Contacts

    emails = fields.List(
        fields.String(validate=[validate.Length(min=1)]), required=True
    )
    phones = fields.List(
        fields.Integer(validate=[validate.Range(min=1)])
    )
    telegram_logins = fields.List(
        fields.String(validate=[validate.Length(min=1)])
    )


class SettingsV2Schema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.SettingsV2

    contacts = fields.Nested(ContactsSchema, required=True)
    notifications = fields.Nested(EmailNotificationsSchema, required=True)
    sms_notifications = fields.Nested(SmsNotificationsSchema)
    booking = fields.Nested(BookingSchema)
    requests = fields.Nested(RequestsSchema)


class FetchSettingsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.FetchSettings

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


class UpdateSettingsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.UpdateSettings

    biz_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    settings = fields.Nested(SettingsSchema, required=True)

    @post_load
    def _unpack_settings(self, data: dict) -> dict:
        settings = data.pop("settings")
        data.update(settings)

        return data


class UpdateSettingsV2Schema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.UpdateSettingsV2

    biz_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    settings = fields.Nested(SettingsV2Schema, required=True)

    @post_load
    def _unpack_settings(self, data: dict) -> dict:
        settings = data.pop("settings")
        data.update(settings)

        return data


class GeneralScheduleIdSettingSchema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.GeneralScheduleIdSetting

    general_schedule_id = fields.Integer(required=False)


class UpdateGeneralScheduleIdSettingSchema(ProtobufSchema):
    class Meta:
        pb_message_class = settings_pb2.UpdateGeneralScheduleIdSetting

    biz_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    general_schedule_id = fields.Integer(
        required=False, missing=None, validate=[validate.Range(min=1)]
    )


class PermissionSchema(ProtobufSchema):
    class Meta:
        pb_message_class = permissions_pb2.Permission

    biz_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    passport_uid = fields.Integer(required=True, validate=[validate.Range(min=1)])
    flags = fields.List(
        PbEnumField(
            enum=PermissionFlag,
            pb_enum=permissions_pb2.PermissionFlag,
            values_map=PROTO_TO_ENUM_MAP["permission_flags"],
        )
    )


class FetchPermissionsInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = permissions_pb2.FetchPermissionsInput

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


class FetchPermissionsOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = permissions_pb2.FetchPermissionsOutput

    permissions = fields.List(fields.Nested(PermissionSchema), required=True)


class CheckPermissionInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = permissions_pb2.CheckPermissionInput

    biz_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    passport_uid = fields.Integer(required=True, validate=[validate.Range(min=1)])
    flag = PbEnumField(
        enum=PermissionFlag,
        pb_enum=permissions_pb2.PermissionFlag,
        values_map=PROTO_TO_ENUM_MAP["permission_flags"],
        required=True,
    )


class CheckPermissionOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = permissions_pb2.CheckPermissionOutput

    has_permission = fields.Bool()


class UpdatePermissionInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = permissions_pb2.UpdatePermissionInput

    biz_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    passport_uid = fields.Integer(required=True, validate=[validate.Range(min=1)])
    flags = fields.List(
        PbEnumField(
            enum=PermissionFlag,
            pb_enum=permissions_pb2.PermissionFlag,
            values_map=PROTO_TO_ENUM_MAP["permission_flags"],
        )
    )


class UpdatePermissionOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = permissions_pb2.UpdatePermissionOutput

    biz_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    passport_uid = fields.Integer(required=True, validate=[validate.Range(min=1)])
    flags = fields.List(
        PbEnumField(
            enum=PermissionFlag,
            pb_enum=permissions_pb2.PermissionFlag,
            values_map=PROTO_TO_ENUM_MAP["permission_flags"],
        )
    )


class TelegramUserSchema(ProtobufSchema):
    class Meta:
        pb_message_class = telegram_pb2.TelegramUser

    user_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    user_login = fields.String(required=True, validate=[validate.Length(min=1)])


class FetchTelegramUsersInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = telegram_pb2.FetchTelegramUsersInput

    user_logins = fields.List(
        fields.String(validate=[validate.Length(min=1)]), required=True
    )


class FetchTelegramUsersOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = telegram_pb2.FetchTelegramUsersOutput

    users = fields.List(fields.Nested(TelegramUserSchema), required=True)


class CheckTelegramUserInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = telegram_pb2.CheckTelegramUserInput

    user_login = fields.String(validate=[validate.Length(min=1)])


class CheckTelegramUserOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = telegram_pb2.CheckTelegramUserOutput

    is_authorized = fields.Bool()


class UpdateTelegramUserInputSchema(Schema):
    user_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    user_login = fields.String(required=True, validate=[validate.Length(min=1)])


class UpdateTelegramUserOutputSchema(Schema):
    user_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    user_login = fields.String(required=True, validate=[validate.Length(min=1)])


class DeleteTelegramUserInputSchema(Schema):
    user_id = fields.Integer(required=True, validate=[validate.Range(min=1)])


class ApiProvider:
    _domain: Domain

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

    @with_schemas(input_schema=FetchSettingsSchema, output_schema=SettingsSchema)
    async def fetch_settings(self, **kwargs):
        return await self._domain.fetch_settings(**kwargs)

    @with_schemas(input_schema=UpdateSettingsSchema, output_schema=SettingsSchema)
    async def update_settings(self, **kwargs):
        return await self._domain.update_settings(**kwargs)

    @with_schemas(input_schema=FetchSettingsSchema, output_schema=SettingsV2Schema)
    async def fetch_settings_v2(self, **kwargs):
        return await self._domain.fetch_settings_v2(**kwargs)

    @with_schemas(input_schema=UpdateSettingsV2Schema, output_schema=SettingsV2Schema)
    async def update_settings_v2(self, **kwargs):
        return await self._domain.update_settings_v2(**kwargs)

    @with_schemas(input_schema=FetchPermissionsInputSchema, output_schema=FetchPermissionsOutputSchema)
    async def fetch_permissions(self, **kwargs):
        return await self._domain.fetch_permissions(**kwargs)

    @with_schemas(input_schema=CheckPermissionInputSchema, output_schema=CheckPermissionOutputSchema)
    async def check_permission(self, **kwargs):
        return await self._domain.check_permission(**kwargs)

    @with_schemas(input_schema=UpdatePermissionInputSchema, output_schema=UpdatePermissionOutputSchema)
    async def update_permission(self, **kwargs):
        return await self._domain.update_permission(**kwargs)

    @with_schemas(
        input_schema=FetchSettingsSchema, output_schema=GeneralScheduleIdSettingSchema
    )
    async def fetch_general_schedule_id_setting(self, **kwargs):
        return await self._domain.fetch_settings(**kwargs)

    @with_schemas(
        input_schema=UpdateGeneralScheduleIdSettingSchema,
        output_schema=GeneralScheduleIdSettingSchema,
    )
    async def update_general_schedule_id_setting(self, **kwargs):
        return await self._domain.update_settings(**kwargs)

    @with_schemas(input_schema=FetchTelegramUsersInputSchema, output_schema=FetchTelegramUsersOutputSchema)
    async def fetch_telegram_users(self, **kwargs):
        return await self._domain.fetch_telegram_users(**kwargs)

    @with_schemas(input_schema=CheckTelegramUserInputSchema, output_schema=CheckTelegramUserOutputSchema)
    async def check_telegram_user(self, **kwargs):
        return await self._domain.check_telegram_user(**kwargs)

    async def update_telegram_user(self, data):
        user = UpdateTelegramUserInputSchema().load(data).data
        result = await self._domain.update_telegram_user(**user)
        return UpdateTelegramUserOutputSchema().dump(result).data

    async def delete_telegram_user(self, data):
        user = DeleteTelegramUserInputSchema().load(data).data
        await self._domain.delete_telegram_user(**user)
