from datetime import datetime, timezone
from typing import List

from google.protobuf.timestamp_pb2 import Timestamp
from marshmallow import fields, pre_dump
from smb.common.http_client import collect_errors

from smb.common.aiotvm import HttpClientWithTvm
from maps_adv.common.protomallow import PbDateTimeField, ProtobufSchema
from maps_adv.geosmb.booking_yang.proto.orders_pb2 import (
    ActualOrderItem,
    ActualOrdersInput,
    ActualOrdersOutput,
    ClearOrdersForGdprInput,
    ClearOrdersForGdprOutput,
    SearchOrdersForGdprInput,
    SearchOrdersForGdprOutput,
)

from .exceptions import InvalidDatetimeTZ


class ActualOrderItemSchema(ProtobufSchema):
    class Meta:
        pb_message_class = 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 = ActualOrdersOutput

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


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

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

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


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

    orders_exist = fields.Boolean(required=True)

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


class BookingYangClient(HttpClientWithTvm):
    @collect_errors
    async def fetch_actual_orders(self, actual_on: datetime) -> List[dict]:
        if actual_on.tzinfo != timezone.utc:
            raise InvalidDatetimeTZ(
                f"Passed active_on date should be in UTC, "
                f"active_on={actual_on.isoformat()}"
            )

        response_body = await self.request(
            method="POST",
            uri="/v1/fetch_actual_orders/",
            expected_statuses=[200],
            data=ActualOrdersInput(
                actual_on=Timestamp(seconds=int(actual_on.timestamp()))
            ).SerializeToString(),
            metric_name="/v1/fetch_actual_orders/",
        )

        return ActualOrdersOutputSchema().from_bytes(response_body)["orders"]

    @collect_errors
    async def clear_orders_for_gdpr(self, passport_uid: int) -> List[int]:
        response_body = await self.request(
            method="POST",
            uri="/internal/v1/clear_orders_for_gdpr/",
            expected_statuses=[200],
            data=ClearOrdersForGdprInput(passport_uid=passport_uid).SerializeToString(),
            metric_name="/internal/v1/clear_orders_for_gdpr/",
        )

        data = ClearOrdersForGdprOutputSchema().from_bytes(response_body)
        return data["cleared_order_ids"]

    @collect_errors
    async def search_orders_for_gdpr(self, passport_uid: int) -> List[dict]:
        response_body = await self.request(
            method="POST",
            uri="/internal/v1/search_orders_for_gdpr/",
            expected_statuses=[200],
            data=SearchOrdersForGdprInput(
                passport_uid=passport_uid
            ).SerializeToString(),
            metric_name="/internal/v1/search_orders_for_gdpr/",
        )

        data = SearchOrdersForGdprOutputSchema().from_bytes(response_body)
        return data["orders_exist"]
