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

from maps_adv.common.protomallow import (
    PbDateTimeField,
    PbDecimalField,
    PbEnumField,
    ProtobufSchema,
)
from maps_adv.geosmb.telegraphist.proto.v2 import (
    common_pb2,
    notifications_for_business_pb2,
    notifications_pb2,
)
from maps_adv.geosmb.telegraphist.server.lib.enums import Transport

PROTO_TO_ENUM_MAP = {
    "transports": [
        (common_pb2.Transport.SMS, Transport.SMS),
        (common_pb2.Transport.EMAIL, Transport.EMAIL),
        (common_pb2.Transport.PUSH, Transport.PUSH),
    ]
}


class RecipientSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_pb2.Recipient

    client_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    biz_id = fields.Integer(required=True, validate=[validate.Range(min=1)])
    phone = fields.Integer(validate=[validate.Range(min=1)])
    email = fields.String(validate=[validate.Length(min=1)])
    device_id = fields.String(validate=[validate.Length(min=1)])
    passport_uid = fields.Integer(validate=[validate.Range(min=1)])


class DiscountSchema(ProtobufSchema):
    class Meta:
        pb_message_class = common_pb2.Discount

    percent = PbDecimalField(places=2, required=True)
    value = PbDecimalField(places=2, required=True)


class CostSchema(ProtobufSchema):
    class Meta:
        pb_message_class = common_pb2.Cost

    final_cost = PbDecimalField(places=2)
    minimal_cost = PbDecimalField(places=2)
    cost_before_discounts = PbDecimalField(places=2)
    discount = fields.Nested(DiscountSchema)

    @validates_schema
    def _validate_required_fields(self, data: dict) -> None:
        if data.get("final_cost") is None and data.get("minimal_cost") is None:
            raise ValidationError(
                "At least one of final_cost or minimal_cost must be specified."
            )


class OrderItemSchema(ProtobufSchema):
    class Meta:
        pb_message_class = common_pb2.OrderItem

    name = fields.String(required=True, validate=[validate.Length(min=1)])
    booking_timestamp = PbDateTimeField(required=True)
    employee_name = fields.String(required=True, validate=[validate.Length(min=1)])
    cost = fields.Nested(CostSchema, required=True)


class OrderSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_pb2.Order

    booking_code = fields.String(required=True, validate=[validate.Length(min=1)])
    items = fields.Nested(
        OrderItemSchema, required=True, many=True, validate=[validate.Length(min=1)]
    )
    total_cost = fields.Nested(CostSchema, required=True)


class OrderCreatedSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_pb2.OrderCreated

    order = fields.Nested(OrderSchema, required=True)
    details_link = fields.String()


class OrderReminderSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_pb2.OrderReminder

    order = fields.Nested(OrderSchema, required=True)
    details_link = fields.String()


class OrderChangedSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_pb2.OrderChanged

    order = fields.Nested(OrderSchema, required=True)
    details_link = fields.String()


class OrderCancelledSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_pb2.OrderCancelled

    order = fields.Nested(OrderSchema, required=True)
    details_link = fields.String()


class NotificationSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_pb2.Notification

    recipient = fields.Nested(RecipientSchema)
    transports = fields.List(
        PbEnumField(
            enum=Transport,
            pb_enum=common_pb2.Transport,
            values_map=PROTO_TO_ENUM_MAP["transports"],
        )
    )
    order_created = fields.Nested(OrderCreatedSchema)
    order_reminder = fields.Nested(OrderReminderSchema)
    order_changed = fields.Nested(OrderChangedSchema)
    order_cancelled = fields.Nested(OrderCancelledSchema)


class NotificationTransportResultSchema(ProtobufSchema):
    class Meta:
        pb_message_class = common_pb2.NotificationTransportResult

    transport = PbEnumField(
        enum=Transport,
        pb_enum=common_pb2.Transport,
        values_map=PROTO_TO_ENUM_MAP["transports"],
        required=True,
    )
    device_id = fields.String()
    passport_uid = fields.Integer()
    phone = fields.Integer()
    email = fields.String()
    error = fields.String()


class BusinessRecipientSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.Recipient

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


class BusinessClientSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.Client

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


class OrderForBusinessSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.Order

    booking_code = fields.String(required=True, validate=[validate.Length(min=1)])
    items = fields.Nested(
        OrderItemSchema, required=True, many=True, validate=[validate.Length(min=1)]
    )
    total_cost = fields.Nested(CostSchema, required=True)
    source = fields.String(required=True, validate=[validate.Length(min=1)])
    comment = fields.String(validate=[validate.Length(min=1)])


class OrderCreatedForBusinessSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.OrderCreated

    client = fields.Nested(BusinessClientSchema, required=True)
    order = fields.Nested(OrderForBusinessSchema, required=True)
    details_link = fields.String(validate=[validate.Length(min=1)])


class OrderChangedForBusinessSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.OrderChanged

    client = fields.Nested(BusinessClientSchema, required=True)
    order = fields.Nested(OrderForBusinessSchema, required=True)
    details_link = fields.String(validate=[validate.Length(min=1)])


class OrderCancelledForBusinessSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.OrderCancelled

    client = fields.Nested(BusinessClientSchema, required=True)
    order = fields.Nested(OrderForBusinessSchema, required=True)
    details_link = fields.String(validate=[validate.Length(min=1)])


class ValidityPeriodSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.ValidityPeriod

    valid_from = PbDateTimeField(required=True)
    valid_to = PbDateTimeField(required=True)


class CertificateBaseDetailsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.CertificateBaseDetails

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


class CertificateForExpirationNotification(ProtobufSchema):
    class Meta:
        pb_message_class = (
            notifications_for_business_pb2.CertificateForExpirationNotification
        )

    base_details = fields.Nested(CertificateBaseDetailsSchema, required=True)
    sales = PbDecimalField(places=2, required=False)

    @post_load
    def unpack_base_details(self, data: dict) -> None:
        base_details = data.pop("base_details")
        data.update(base_details)


class CertificateFullDetailsSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.CertificateFullDetails

    base_details = fields.Nested(CertificateBaseDetailsSchema, required=True)
    price = PbDecimalField(places=2, required=True)
    discount = PbDecimalField(places=2, required=True)
    validity_period = fields.Nested(ValidityPeriodSchema, required=True)

    @post_load
    def unpack_base_details(self, data: dict) -> None:
        base_details = data.pop("base_details")
        data.update(base_details)


class CertificateExpiringSchemaSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.CertificateExpiring

    certificate = fields.Nested(CertificateForExpirationNotification, required=True)
    create_new_link = fields.String(required=True, validate=[validate.Length(min=1)])


class CertificateExpiredSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.CertificateExpired

    certificate = fields.Nested(CertificateForExpirationNotification, required=True)
    create_new_link = fields.String(required=True, validate=[validate.Length(min=1)])


class CertificateConnectPaymentSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.CertificateConnectPayment

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


class CertificateRejectedSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.CertificateRejected

    certificate = fields.Nested(CertificateBaseDetailsSchema, required=True)


class FirstCertificateApprovedSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.FirstCertificateApproved

    certificate = fields.Nested(CertificateBaseDetailsSchema, required=True)


class SubsequentCertificateApprovedSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.SubsequentCertificateApproved

    certificate = fields.Nested(CertificateBaseDetailsSchema, required=True)


class CertificatePurchasedSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.CertificatePurchased

    certificate = fields.Nested(CertificateFullDetailsSchema, required=True)
    cashier_link = fields.String(required=True, validate=[validate.Length(min=1)])


class CertificateCreatedSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.CertificateCreated

    certificate = fields.Nested(CertificateBaseDetailsSchema, required=True)


class RequestCreatedForBusinessSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.RequestCreated

    details_link = fields.String(required=True)


class NotificationForBusinessSchema(ProtobufSchema):
    class Meta:
        pb_message_class = notifications_for_business_pb2.Notification

    recipient = fields.Nested(BusinessRecipientSchema)
    transports = fields.List(
        PbEnumField(
            enum=Transport,
            pb_enum=common_pb2.Transport,
            values_map=PROTO_TO_ENUM_MAP["transports"],
        )
    )
    order_created = fields.Nested(OrderCreatedForBusinessSchema)
    order_changed = fields.Nested(OrderChangedForBusinessSchema)
    order_cancelled = fields.Nested(OrderCancelledForBusinessSchema)
    certificate_expiring = fields.Nested(CertificateExpiringSchemaSchema)
    certificate_expired = fields.Nested(CertificateExpiredSchema)
    certificate_connect_payment = fields.Nested(CertificateConnectPaymentSchema)
    certificate_rejected = fields.Nested(CertificateRejectedSchema)
    first_certificate_approved = fields.Nested(FirstCertificateApprovedSchema)
    subsequent_certificate_approved = fields.Nested(SubsequentCertificateApprovedSchema)
    certificate_purchased = fields.Nested(CertificatePurchasedSchema)
    certificate_created = fields.Nested(CertificateCreatedSchema)
    request_created = fields.Nested(RequestCreatedForBusinessSchema)


class NotificationResultSchema(ProtobufSchema):
    class Meta:
        pb_message_class = common_pb2.NotificationResult

    results = fields.Nested(NotificationTransportResultSchema, many=True)

    @pre_dump
    def _to_flat(self, data: dict) -> dict:
        return {
            "results": [
                {"transport": transport, **result}
                for d in data
                for transport, result in d.items()
            ]
        }
