from typing import List

import aiohttp
from marshmallow import fields, post_load
from smb.common.http_client import HttpClient, collect_errors

from maps_adv.common.protomallow import ProtobufSchema
from maps_adv.geosmb.clients.bvm.proto.bvm_pb2 import (
    ClientError,
    ClientErrorCode,
    FetchBizIdInput,
    FetchBizIdOutput,
    FetchNoCreateBizIdInput,
    FetchNoCreateBizIdOutput,
    FetchPermalinksByBizIdInput,
    FetchPermalinksByBizIdOutput,
    FetchPermalinksByBizIdsInput,
    FetchPermalinksByBizIdsOutput,
)

__all__ = ["BvmClient", "BvmBadProto", "BvmNotFound", "BvmUnknownError"]


class BvmException(Exception):
    pass


class BvmBadProto(BvmException):
    pass


class BvmNotFound(BvmException):
    pass


class BvmUnknownError(BvmException):
    pass


MAP_PROTO_ERROR_TO_EXCEPTION = {
    ClientErrorCode.UNKNOWN: lambda e: BvmUnknownError(e.message),
    ClientErrorCode.NOT_FOUND: lambda e: BvmNotFound(e.message),
    ClientErrorCode.BAD_PROTO: lambda e: BvmBadProto(e.message),
}


class BizIdDataSchema(ProtobufSchema):
    class Meta:
        pb_message_class = FetchPermalinksByBizIdsOutput.BizIdData

    biz_id = fields.Integer(required=True)
    permalinks = fields.List(fields.Integer())


class FetchPermalinksByBizIdsOutputSchema(ProtobufSchema):
    class Meta:
        pb_message_class = FetchPermalinksByBizIdsOutput

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

    @post_load
    def to_dict(self, data):
        return {item["biz_id"]: item["permalinks"] for item in data["items"]}


class BvmClient(HttpClient):
    @collect_errors
    async def fetch_biz_id_by_permalink(self, permalink: int) -> int:
        response_body = await self.request(
            method="POST",
            uri="/v1/fetch_biz_id",
            expected_statuses=[200],
            headers={"Content-Type": "application/x-protobuf"},
            data=FetchBizIdInput(permalink=permalink).SerializeToString(),
            metric_name="/v1/fetch_biz_id",
        )

        return FetchBizIdOutput.FromString(response_body).biz_id

    @collect_errors
    async def fetch_biz_id_no_create_by_permalink(self, permalink: int) -> int:
        response_body = await self.request(
            method="POST",
            uri="/v1/fetch_no_create_biz_id",
            expected_statuses=[200],
            headers={"Content-Type": "application/x-protobuf"},
            data=FetchNoCreateBizIdInput(permalink=permalink).SerializeToString(),
            metric_name="/v1/fetch_no_create_biz_id",
        )

        return FetchNoCreateBizIdOutput.FromString(response_body).biz_id

    @collect_errors
    async def fetch_permalinks_by_biz_id(self, biz_id: int) -> List[int]:
        response_body = await self.request(
            method="POST",
            uri="/v1/fetch_permalinks",
            expected_statuses=[200],
            headers={"Content-Type": "application/x-protobuf"},
            data=FetchPermalinksByBizIdInput(biz_id=biz_id).SerializeToString(),
            metric_name="/v1/fetch_permalinks",
        )

        return list(FetchPermalinksByBizIdOutput.FromString(response_body).permalinks)

    @collect_errors
    async def fetch_permalinks_by_biz_ids(self, biz_ids: List[int]) -> dict:
        response_body = await self.request(
            method="POST",
            uri="/v1/fetch_approximate_permalinks_for_biz_ids",
            expected_statuses=[200],
            headers={"Content-Type": "application/x-protobuf"},
            data=FetchPermalinksByBizIdsInput(biz_ids=biz_ids).SerializeToString(),
            metric_name="/v1/fetch_approximate_permalinks_for_biz_ids",
        )

        return FetchPermalinksByBizIdsOutputSchema().from_bytes(response_body)

    async def _handle_custom_errors(self, response: aiohttp.ClientResponse) -> None:
        if response.status == 400:
            response_body = await response.read()
            error = ClientError.FromString(response_body)

            raise MAP_PROTO_ERROR_TO_EXCEPTION[error.code](error)
        else:
            await self._raise_unknown_response(response)
