import json
from datetime import timedelta
from decimal import Decimal
from typing import Dict, Optional, TypeVar, Union

from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
from marshmallow import (
    ValidationError,
    fields,
    post_load,
    pre_dump,
    validate,
    validates_schema,
)

from maps_adv.common.protomallow import (
    PbDateTimeField,
    PbEnumField,
    PbStringEnumField,
    ProtobufSchema,
    with_schemas,
)
from maps_adv.geosmb.clients.market import ClientAction as ServiceActionType
from maps_adv.geosmb.landlord.proto import (
    common_pb2,
    contacts_pb2,
    counters_pb2,
    generate_pb2,
    goods_pb2,
    instagram_pb2,
    landing_config_pb2,
    organization_details_pb2,
    preferences_pb2,
    promo_pb2,
    rating_pb2,
    schedule_pb2,
    services_pb2,
)
from maps_adv.geosmb.landlord.proto.internal import (
    instagram_internal_pb2,
    landing_details_pb2,
    suggests_pb2,
)
from maps_adv.geosmb.landlord.server.lib.domain import Domain
from maps_adv.geosmb.landlord.server.lib.enums import LandingVersion, ServiceItemType

ProtoEnumType = TypeVar("ProtoEnumType", bound=EnumTypeWrapper)


class TimedeltaField(fields.Field):
    def _serialize(self, value, attr, obj):
        if not isinstance(value, timedelta):
            raise ValidationError(f"timedelta expected, got {type(value)}")

        return value.seconds

    def _deserialize(self, value, attr, data):
        if not isinstance(value, int):
            raise ValidationError(f"int expected, got {type(value)}")

        return timedelta(seconds=value)


class GenerateDataInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = generate_pb2.GenerateDataInput

    biz_id = fields.Integer(validate=[validate.Range(min=1)])
    permalink = fields.Integer(validate=[validate.Range(min=1)])
    publish = fields.Boolean(missing=False)

    @validates_schema
    def validate_has_param(self, data, **kwargs):
        if "biz_id" not in data and "permalink" not in data:
            raise ValidationError("Exactly one of biz_id and permalink required")


class GenerateDataOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = generate_pb2.GenerateDataOutput

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

    @pre_dump
    def _to_dict(self, data: str) -> dict:
        return {"slug": data}


class DeleteLandingInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.DeleteLandingInput

    biz_id = fields.Integer(required=True)


class OrganizationDetailsInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = organization_details_pb2.OrganizationDetailsInput

    slug = fields.String(required=True, validate=[validate.Length(min=1)])
    token = fields.String(required=True, validate=[validate.Length(min=1)])
    version = PbEnumField(
        enum=LandingVersion,
        pb_enum=organization_details_pb2.LandingVersion,
        values_map=[
            (organization_details_pb2.LandingVersion.STABLE, LandingVersion.STABLE),
            (organization_details_pb2.LandingVersion.UNSTABLE, LandingVersion.UNSTABLE),
        ],
        missing=organization_details_pb2.LandingVersion.STABLE,
    )


class ImageTemplateSchema(ProtobufSchema):
    class Meta:
        pb_message_class = common_pb2.ImageTemplate

    template_url = fields.String(required=True)

    @pre_dump
    def _to_dict(self, data: Optional[str]) -> dict:
        return {"template_url": data}

    @post_load
    def _from_dict(self, data: dict) -> Optional[str]:
        return data.get("template_url")


class DecimalSchema(ProtobufSchema):
    class Meta:
        pb_message_class = common_pb2.Decimal

    value = fields.String(required=True)

    @pre_dump
    def _to_dict(self, data: Union[Decimal, str]) -> dict:
        return {"value": "{:f}".format(Decimal(data).normalize())}

    @post_load
    def _to_str(self, data):
        return data["value"]


class ColorThemeSchema(ProtobufSchema):
    class Meta:
        pb_message_class = preferences_pb2.ColorTheme

    theme = PbStringEnumField(
        pb_enum=preferences_pb2.ColorTheme.ColorTone, required=True
    )
    main_color_hex = fields.String(required=True)
    text_color_over_main = PbStringEnumField(
        pb_enum=preferences_pb2.ColorTheme.ColorTone, required=True
    )
    main_color_name = fields.String()


class InternalColorThemeSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.ColorTheme

    theme = PbStringEnumField(
        pb_enum=landing_details_pb2.ColorTheme.ColorTone, required=True
    )
    preset = fields.String(required=True)


class CTAButtonSchema(ProtobufSchema):
    class Meta:
        pb_message_class = preferences_pb2.CTAButton

    predefined = PbStringEnumField(pb_enum=preferences_pb2.CTAButton.PredefinedType)
    custom = fields.String()
    value = fields.String(required=True)


class GoogleAdsCounterSchema(ProtobufSchema):
    class Meta:
        pb_message_class = counters_pb2.GoogleAdsCounter

    id = fields.String()
    goals = fields.Dict(keys=fields.String(), values=fields.String())


class TikTokPixelSchema(ProtobufSchema):
    class Meta:
        pb_message_class = counters_pb2.TikTokPixel

    id = fields.String()
    goals = fields.Dict(keys=fields.String(), values=fields.String())


class VkPixelSchema(ProtobufSchema):
    class Meta:
        pb_message_class = counters_pb2.VkPixel

    id = fields.String()
    goals = fields.Dict(keys=fields.String(), values=fields.String())


class SocialButtonSchema(ProtobufSchema):
    class Meta:
        pb_message_class = preferences_pb2.SocialButton

    type = PbStringEnumField(pb_enum=preferences_pb2.SocialButton.Type, required=True)
    url = fields.String(required=True)
    custom_text = fields.String()


class PreferencesSchema(ProtobufSchema):
    class Meta:
        pb_message_class = preferences_pb2.Preferences

    personal_metrika_code = fields.String()
    external_metrika_code = fields.String(dump_only=True)
    google_counters = fields.Nested(GoogleAdsCounterSchema, many=True)
    tiktok_pixels = fields.Nested(TikTokPixelSchema, many=True)
    vk_pixels = fields.Nested(VkPixelSchema, many=True)
    color_theme = fields.Nested(ColorThemeSchema, required=True)
    cta_button = fields.Nested(CTAButtonSchema)
    cart_enabled = fields.Boolean()
    social_buttons = fields.Nested(SocialButtonSchema, many=True)


class InternalPreferencesSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.Preferences

    personal_metrika_code = fields.String()
    color_theme = fields.Nested(InternalColorThemeSchema, required=True)
    cta_button = fields.Nested(CTAButtonSchema)
    cart_enabled = fields.Boolean()
    social_buttons = fields.Nested(SocialButtonSchema, many=True)


class ContactsInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.ContactsInput

    email = fields.String()
    phone = fields.String()
    phones = fields.List(fields.String())
    website = fields.String()
    instagram = fields.String()
    facebook = fields.String()
    vkontakte = fields.String()
    twitter = fields.String()
    telegram = fields.String()
    viber = fields.String()
    whatsapp = fields.String()


class RegionSchema(ProtobufSchema):
    class Meta:
        pb_message_class = contacts_pb2.Region

    preposition = fields.String(required=True)
    prepositional_case = fields.String(required=True)


class AreaSchema(ProtobufSchema):
    class Meta:
        pb_message_class = contacts_pb2.Area

    regions = fields.Nested(RegionSchema, many=True)


class ServiceAreaSchema(ProtobufSchema):
    class Meta:
        pb_message_class = contacts_pb2.ServiceArea

    service_radius_km = fields.Integer()
    area = fields.Nested(AreaSchema)


class GeoSchema(ProtobufSchema):
    class Meta:
        pb_message_class = contacts_pb2.Geo

    permalink = fields.String(required=True)
    lon = fields.Nested(DecimalSchema, required=True)
    lat = fields.Nested(DecimalSchema, required=True)
    address = fields.String(required=True)
    address_is_accurate = fields.Bool()
    service_area = fields.Nested(ServiceAreaSchema)
    locality = fields.String()
    country_code = fields.String()
    postal_code = fields.String()
    address_region = fields.String()
    street_address = fields.String()


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

    geo = fields.Nested(GeoSchema)
    email = fields.String()
    phone = fields.String()
    website = fields.String()
    phones = fields.List(fields.String())
    instagram = fields.String()
    facebook = fields.String()
    vkontakte = fields.String()
    twitter = fields.String()
    telegram = fields.String()
    viber = fields.String()
    whatsapp = fields.String()
    is_substitution_phone = fields.Bool()


class ScheduleItemSchema(ProtobufSchema):
    class Meta:
        pb_message_class = schedule_pb2.ScheduleItem

    day = PbStringEnumField(pb_enum=schedule_pb2.ScheduleItem.DayOfWeek, required=True)
    opens_at = fields.Integer(required=True, validate=[validate.Range(min=0)])
    closes_at = fields.Integer(required=True, validate=[validate.Range(min=0)])


class ScheduleSchema(ProtobufSchema):
    class Meta:
        pb_message_class = schedule_pb2.Schedule

    tz_offset = TimedeltaField()
    schedule = fields.Nested(ScheduleItemSchema, many=True)
    work_now_text = fields.String()


class UserReviewSchema(ProtobufSchema):
    class Meta:
        pb_message_class = rating_pb2.UserReview

    username = fields.String(required=True)
    userpic = fields.Nested(ImageTemplateSchema)
    created_at = PbDateTimeField(required=True)
    rating = fields.Nested(DecimalSchema, required=True)
    text = fields.String(required=True)


class RatingSchema(ProtobufSchema):
    class Meta:
        pb_message_class = rating_pb2.Rating

    aggregated_rating = fields.Nested(DecimalSchema)
    review_count = fields.Integer(validate=[validate.Range(min=0)])
    reviews = fields.Nested(UserReviewSchema, many=True)


class ExtrasSchema(ProtobufSchema):
    class Meta:
        pb_message_class = organization_details_pb2.Extras

    plain_extras = fields.List(fields.String())
    extended_description = fields.String()


class PromotionSchema(ProtobufSchema):
    class Meta:
        pb_message_class = promo_pb2.Promotion

    announcement = fields.String(required=True)
    description = fields.String(required=True)
    date_from = PbDateTimeField(required=True)
    date_to = PbDateTimeField(required=True)
    details_url = fields.String()
    banner_img = fields.Nested(ImageTemplateSchema)


class PromosSchema(ProtobufSchema):
    class Meta:
        pb_message_class = promo_pb2.Promos

    promotion = fields.Nested(PromotionSchema, many=True)


class ServiceActionTypeSettingsLinkSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.ServiceActionTypeSettingsLink

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


class ServiceActionTypeSettingsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.ServiceActionTypeSettings

    link = fields.Nested(ServiceActionTypeSettingsLinkSchema)


class ServiceItemSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.ServiceItem

    name = fields.String(required=True)
    image = fields.Nested(ImageTemplateSchema)
    description = fields.String()
    categories = fields.List(fields.String())
    cost = fields.Nested(DecimalSchema)
    min_cost = fields.Nested(DecimalSchema)
    type = PbEnumField(
        enum=ServiceItemType,
        pb_enum=services_pb2.ServiceItem.ServiceItemType,
        values_map=[
            (
                services_pb2.ServiceItem.ServiceItemType.PROMOTED,
                ServiceItemType.PROMOTED,
            ),
            (services_pb2.ServiceItem.ServiceItemType.MARKET, ServiceItemType.MARKET),
        ],
        required=True,
    )
    id = fields.Integer(reqiured=True)
    duration = fields.Integer()
    min_duration = fields.Integer()
    action_type = PbEnumField(
        enum=ServiceActionType,
        pb_enum=services_pb2.ServiceItem.ServiceActionType,
        values_map=[
            (
                services_pb2.ServiceItem.ServiceActionType.BOOKING_WITH_EMPLOYEE,
                ServiceActionType.BOOKING_WITH_EMPLOYEE,
            ),
            (
                services_pb2.ServiceItem.ServiceActionType.BOOKING_WITHOUT_EMPLOYEE,
                ServiceActionType.BOOKING_WITHOUT_EMPLOYEE,
            ),
            (
                services_pb2.ServiceItem.ServiceActionType.REQUEST,
                ServiceActionType.REQUEST,
            ),
            (services_pb2.ServiceItem.ServiceActionType.CALL, ServiceActionType.CALL),
            (services_pb2.ServiceItem.ServiceActionType.LINK, ServiceActionType.LINK),
        ],
    )
    service_action_type_settings = fields.Nested(ServiceActionTypeSettingsSchema)
    url = fields.String()


class SevicesSchema(ProtobufSchema):
    class Meta:
        pb_message_class = services_pb2.Services

    items = fields.Nested(ServiceItemSchema, many=True)


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

    amount = fields.Integer(required=True)
    currency = PbStringEnumField(pb_enum=common_pb2.Currency, required=True)


class InstagramDataOfferSchema(ProtobufSchema):
    class Meta:
        pb_message_class = instagram_pb2.InstagramData.Offer

    type = PbStringEnumField(pb_enum=instagram_pb2.InstagramData.Offer.Type)
    name = fields.String()
    price = fields.Nested(MoneySchema)
    categories = fields.List(fields.String())


class InstagramDataMediaSchema(ProtobufSchema):
    class Meta:
        pb_message_class = instagram_pb2.InstagramData.Media

    type = PbStringEnumField(
        pb_enum=instagram_pb2.InstagramData.Media.Type, required=True
    )
    media_url = fields.String(required=True)
    preview_url = fields.String()
    internal_url = fields.String()


class InstagramPostSchema(ProtobufSchema):
    class Meta:
        pb_message_class = instagram_pb2.InstagramData.Post

    id = fields.Integer(required=True)
    caption = fields.String()
    media_urls = fields.Nested(InstagramDataMediaSchema, many=True)
    offer = fields.Nested(InstagramDataOfferSchema)


class InstagramDataSchema(ProtobufSchema):
    class Meta:
        pb_message_class = instagram_pb2.InstagramData

    instagram_account = fields.String(required=False)
    posts = fields.Nested(InstagramPostSchema, many=True)


class InstagramSettingsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = instagram_pb2.Settings

    name = fields.String()
    description = fields.String()
    logo = fields.String()


class SocialButtonsInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = instagram_internal_pb2.SocialButtons

    buttons = fields.Nested(SocialButtonSchema, many=True)


class EditInstagramLandingInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = instagram_internal_pb2.EditInstagramLandingInput

    biz_id = fields.Integer(required=True)
    permalink = fields.Integer()
    instagram = fields.Nested(InstagramDataSchema)
    cta_button = fields.Nested(CTAButtonSchema)
    settings = fields.Nested(InstagramSettingsSchema)
    contacts = fields.Nested(ContactsInputSchema)
    social_buttons = fields.Nested(SocialButtonsInputSchema)

    @post_load
    def postload(self, data):
        if "social_buttons" in data:
            data["social_buttons"] = data["social_buttons"].get("buttons", [])


class EditInstagramLandingOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = instagram_internal_pb2.EditInstagramLandingOutput

    slug = fields.String(required=True)


class LandingPhoneInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.LandingPhoneInput

    permalink = fields.Integer(required=True)


class LandingPhoneDataSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.LandingPhoneData

    phone = fields.String()


class BranchDetailsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = organization_details_pb2.BranchDetails

    slug = fields.String(required=True)
    permalink = fields.String(required=True)
    name = fields.String(required=True)
    description = fields.String()
    contacts = fields.Nested(ContactsSchema, required=True)
    logo = fields.Nested(ImageTemplateSchema)
    cover = fields.Nested(ImageTemplateSchema)
    preferences = fields.Nested(PreferencesSchema)
    schedule = fields.Nested(ScheduleSchema)


class CategorySchema(ProtobufSchema):
    class Meta:
        pb_message_class = goods_pb2.Category

    name = fields.String(required=True)


class GoodsDataSchema(ProtobufSchema):
    class Meta:
        pb_message_class = goods_pb2.GoodsData

    goods_available = fields.Boolean(required=True)
    categories = fields.List(fields.Nested(CategorySchema))
    source_name = fields.String()


class OrganizationDetailsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = organization_details_pb2.OrganizationDetails

    biz_id = fields.String()
    name = fields.String(required=True)
    categories = fields.List(fields.String())
    description = fields.String()
    logo = fields.Nested(ImageTemplateSchema)
    cover = fields.Nested(ImageTemplateSchema)
    photos = fields.Nested(ImageTemplateSchema, many=True)
    preferences = fields.Nested(PreferencesSchema, required=True)
    contacts = fields.Nested(ContactsSchema, required=True)
    schedule = fields.Nested(ScheduleSchema)
    rating = fields.Nested(RatingSchema)
    services = fields.Nested(SevicesSchema)
    extras = fields.Nested(ExtrasSchema)
    promos = fields.Nested(PromosSchema)
    goods = fields.Nested(GoodsDataSchema)
    permalink = fields.String(required=True)
    blocked = fields.Boolean()
    published = fields.Boolean()
    landing_type = PbStringEnumField(
        pb_enum=organization_details_pb2.LandingType, required=True
    )
    instagram = fields.Nested(InstagramDataSchema)
    chain_id = fields.Integer()
    branches = fields.List(fields.Nested(BranchDetailsSchema))


class CheckSlugAvailableInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.CheckSlugAvailableInput

    slug = fields.String(required=True, validate=[validate.Length(min=1)])
    biz_id = fields.Integer()


class CheckSlugAvailableOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.CheckSlugAvailableOutput

    available = fields.Bool(required=True)

    @pre_dump
    def _to_dict(self, data: bool) -> dict:
        return {"available": data}


class UpdateSlugInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.UpdateSlugInput

    biz_id = fields.Integer(required=True)
    slug = fields.String(required=True, validate=[validate.Length(min=1)])


class SetLandingPublicityInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.SetLandingPublicityInput

    biz_id = fields.Integer(required=True)
    is_published = fields.Boolean(required=True)


class BlockingDataSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.BlockingData

    blocker_uid = fields.Integer(required=True)
    blocking_description = fields.String(required=True)
    ticket_id = fields.String(required=True)


class SetBlockedInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.SetBlockedInput

    biz_id = fields.Integer(required=True)
    is_blocked = fields.Boolean(required=True)
    blocking_data = fields.Nested(BlockingDataSchema)


class ShowLandingDetailsInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.ShowLandingDetailsInput

    biz_id = fields.Integer(required=True)
    version = PbEnumField(
        enum=LandingVersion,
        pb_enum=organization_details_pb2.LandingVersion,
        values_map=[
            (organization_details_pb2.LandingVersion.STABLE, LandingVersion.STABLE),
            (organization_details_pb2.LandingVersion.UNSTABLE, LandingVersion.UNSTABLE),
        ],
        required=True,
    )


class BlocksOptionsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.BlocksOptions

    show_cover = fields.Bool(required=True)
    show_logo = fields.Bool(required=True)
    show_schedule = fields.Bool(required=True)
    show_photos = fields.Bool(required=True)
    show_map_and_address = fields.Bool(required=True)
    show_services = fields.Bool(required=True)
    show_reviews = fields.Bool(required=True)
    reviews_min_rating = DecimalSchema()
    show_extras = fields.Bool(required=True)
    show_branches = fields.Bool()


class LandingDetailsInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.LandingDetailsInput

    name = fields.String(required=True)
    categories = fields.List(fields.String(), required=True)
    description = fields.String()
    logo = fields.Nested(ImageTemplateSchema)
    cover = fields.Nested(ImageTemplateSchema)
    preferences = fields.Nested(InternalPreferencesSchema, required=True)
    contacts = fields.Nested(ContactsInputSchema, required=True)
    extras = fields.Nested(ExtrasSchema)
    blocks_options = fields.Nested(BlocksOptionsSchema, required=True)


class LandingDetailsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.LandingDetails

    name = fields.String(required=True)
    categories = fields.List(fields.String(), required=True)
    description = fields.String()
    logo = fields.Nested(ImageTemplateSchema)
    cover = fields.Nested(ImageTemplateSchema)
    preferences = fields.Nested(InternalPreferencesSchema, required=True)
    contacts = fields.Nested(ContactsSchema, required=True)
    extras = fields.Nested(ExtrasSchema)
    blocks_options = fields.Nested(BlocksOptionsSchema, required=True)
    blocked = fields.Boolean()
    blocking_data = fields.Nested(BlockingDataSchema)


class ShowLandingDetailsOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.ShowLandingDetailsOutput

    slug = fields.String(required=True)
    landing_details = fields.Nested(LandingDetailsSchema, required=True)
    is_published = fields.Boolean(required=True)


class EditLandingDetailsInput(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.EditLandingDetailsInput

    biz_id = fields.Integer(required=True)
    version = PbEnumField(
        enum=LandingVersion,
        pb_enum=organization_details_pb2.LandingVersion,
        values_map=[
            (organization_details_pb2.LandingVersion.STABLE, LandingVersion.STABLE),
            (organization_details_pb2.LandingVersion.UNSTABLE, LandingVersion.UNSTABLE),
        ],
        required=True,
    )
    landing_details = fields.Nested(LandingDetailsInputSchema, required=True)


class EditLandingDetailsOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.EditLandingDetailsOutput

    slug = fields.String(required=True)
    landing_details = fields.Nested(LandingDetailsSchema, required=True)
    is_published = fields.Boolean(required=True)


class SuggestInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = suggests_pb2.SuggestInput

    biz_id = fields.Integer(required=True)


class PlainSuggestSchema(ProtobufSchema):
    class Meta:
        pb_message_class = suggests_pb2.PlainSuggest

    options = fields.List(fields.String())

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


class CTAButtonSuggestSchema(ProtobufSchema):
    class Meta:
        pb_message_class = suggests_pb2.CTAButtonSuggest

    available_buttons = fields.Nested(CTAButtonSchema, many=True)
    available_types = fields.List(
        PbStringEnumField(pb_enum=preferences_pb2.CTAButton.PredefinedType)
    )


class ColorPresetsSuggestSchema(ProtobufSchema):
    class Meta:
        pb_message_class = suggests_pb2.ColorPresetsSuggest

    class _ColorPresetSchema(ProtobufSchema):
        class Meta:
            pb_message_class = suggests_pb2.ColorPreset

        preset = fields.String(required=True)
        main_color_hex = fields.String(required=True)

    options = fields.Nested(_ColorPresetSchema, many=True)

    @pre_dump
    def _to_dict(self, data: Dict[str, Dict[str, str]]) -> dict:
        return {"options": [dict(preset=preset, **d) for preset, d in data.items()]}


class LandingConfigInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_config_pb2.LandingConfigInput

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


class LandingConfigSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_config_pb2.LandingConfig

    json_config = fields.String()

    @pre_dump
    def _to_dict(self, data: dict) -> dict:
        return {"json_config": json.dumps(data)}


class PhotoSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.Photo

    id = fields.String(required=False)
    url = fields.String(required=True)
    hidden = fields.Boolean(missing=False)


class ShowLandingPhotoInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.ShowLandingPhotoInput

    biz_id = fields.Integer(required=True)
    version = PbEnumField(
        enum=LandingVersion,
        pb_enum=organization_details_pb2.LandingVersion,
        values_map=[
            (organization_details_pb2.LandingVersion.STABLE, LandingVersion.STABLE),
            (organization_details_pb2.LandingVersion.UNSTABLE, LandingVersion.UNSTABLE),
        ],
        missing=organization_details_pb2.LandingVersion.STABLE,
    )


class ShowLandingPhotoOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.ShowLandingPhotoOutput

    photos = fields.Nested(PhotoSchema, many=True)


class HideLandingPhotoInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.HideLandingPhotoInput

    biz_id = fields.Integer(required=True)
    version = PbEnumField(
        enum=LandingVersion,
        pb_enum=organization_details_pb2.LandingVersion,
        values_map=[
            (organization_details_pb2.LandingVersion.STABLE, LandingVersion.STABLE),
            (organization_details_pb2.LandingVersion.UNSTABLE, LandingVersion.UNSTABLE),
        ],
        missing=organization_details_pb2.LandingVersion.STABLE,
    )
    photo_id_to_hidden = fields.Dict(keys=fields.Str(), values=fields.Bool())


class CreateLandingInputDataSchema(ProtobufSchema):
    class Meta:
        pb_message_class = organization_details_pb2.CreateLandingInputData

    permalink = fields.Integer(required=True)
    name = fields.String(required=True)
    categories = fields.List(fields.String())
    contacts = fields.Nested(ContactsSchema)


class PermalinkInputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = landing_details_pb2.PermalinkInput

    permalink = fields.Integer(required=True)


class ApiProvider:
    _domain: Domain

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

    @with_schemas(
        input_schema=GenerateDataInputSchema, output_schema=GenerateDataOutputSchema
    )
    async def generate_landing_data(self, **kwargs):
        return await self._domain.generate_data_for_biz_id(**kwargs)

    @with_schemas(
        input_schema=CreateLandingInputDataSchema,
        output_schema=GenerateDataOutputSchema,
    )
    async def create_landing_from_data(self, **kwargs):
        return await self._domain.create_landing_from_data(**kwargs)

    @with_schemas(input_schema=DeleteLandingInputSchema)
    async def delete_landing(self, **kwargs):
        return await self._domain.delete_landing_by_biz_id(**kwargs)

    @with_schemas(
        input_schema=OrganizationDetailsInputSchema,
        output_schema=OrganizationDetailsSchema,
    )
    async def fetch_published_landing_data(self, **kwargs):
        return await self._domain.fetch_published_landing_data_by_slug(**kwargs)

    @with_schemas(
        input_schema=LandingConfigInputSchema,
        output_schema=LandingConfigSchema,
    )
    async def get_landing_config(self, **kwargs):
        return await self._domain.get_landing_config(**kwargs)

    @with_schemas(
        input_schema=CheckSlugAvailableInputSchema,
        output_schema=CheckSlugAvailableOutputSchema,
    )
    async def check_slug_is_free(self, **kwargs):
        return await self._domain.check_slug_is_free(**kwargs)

    @with_schemas(input_schema=UpdateSlugInputSchema)
    async def update_slug(self, **kwargs):
        return await self._domain.update_biz_state_slug(**kwargs)

    @with_schemas(input_schema=SetLandingPublicityInputSchema)
    async def set_landing_publicity(self, **kwargs):
        return await self._domain.set_landing_publicity(**kwargs)

    @with_schemas(input_schema=SetBlockedInputSchema)
    async def set_blocked(self, **kwargs):
        return await self._domain.update_biz_state_set_blocked(**kwargs)

    @with_schemas(
        input_schema=ShowLandingDetailsInputSchema,
        output_schema=ShowLandingDetailsOutputSchema,
    )
    async def show_landing_details(self, **kwargs):
        return await self._domain.fetch_landing_data_for_crm(**kwargs)

    @with_schemas(
        input_schema=EditLandingDetailsInput,
        output_schema=EditLandingDetailsOutputSchema,
    )
    async def edit_landing_details(self, **kwargs):
        return await self._domain.update_landing_data_from_crm(**kwargs)

    @with_schemas(output_schema=ColorPresetsSuggestSchema)
    async def suggest_color_presets(self):
        return self._domain.color_presets

    @with_schemas(input_schema=SuggestInputSchema)
    async def suggest_field_values(self, field, **kwargs):
        got = await self._domain.suggest_field_values(field=field, **kwargs)

        if field == "cta_button":
            return CTAButtonSuggestSchema().to_bytes(got)
        return PlainSuggestSchema().to_bytes(got)

    @with_schemas(
        input_schema=EditInstagramLandingInputSchema,
        output_schema=EditInstagramLandingOutputSchema,
    )
    async def edit_instagram_landing(self, **kwargs):
        return await self._domain.edit_instagram_landing(**kwargs)

    @with_schemas(
        input_schema=LandingPhoneInputSchema,
        output_schema=LandingPhoneDataSchema,
    )
    async def fetch_landing_phone(self, **kwargs):
        return await self._domain.fetch_landing_phone(**kwargs)

    @with_schemas(
        input_schema=ShowLandingPhotoInputSchema,
        output_schema=ShowLandingPhotoOutputSchema,
    )
    async def fetch_landing_photos(self, **kwargs):
        return await self._domain.fetch_landing_photos(**kwargs)

    @with_schemas(
        input_schema=HideLandingPhotoInputSchema,
    )
    async def hide_landing_photos(self, **kwargs):
        return await self._domain.hide_landing_photos(**kwargs)

    @with_schemas(
        input_schema=PermalinkInputSchema, output_schema=GenerateDataOutputSchema
    )
    async def get_landing_slug(self, **kwargs):
        return await self._domain.get_landing_slug(**kwargs)
