from operator import attrgetter
from typing import Optional

from aiohttp.web import Request, Response, middleware
from marshmallow import ValidationError

from maps_adv.geosmb.clients.bvm import BvmNotFound as BvmUnknownBizId
from maps_adv.geosmb.doorman.client import NotFound as ClientNotFoundInDoorman
from maps_adv.geosmb.telegraphist.proto.errors_pb2 import Error
from maps_adv.geosmb.telegraphist.server.lib.exceptions import (
    NoOrginfo,
    NoOrgsForBizId,
    RecipientDenied,
    UnsupportedTransport,
)
from maps_adv.geosmb.tuner.client import UnknownBizId as TunerUnknownBizId

__all__ = ["handle_validation_errors", "handle_application_errors"]


@middleware
async def handle_validation_errors(req: Request, handler) -> Response:
    try:
        return await handler(req)
    except ValidationError as exc:
        if isinstance(exc.messages, dict):
            description = _dict_to_str(exc.messages)
        else:
            description = ", ".join(sorted(exc.messages))

        error = Error(code=Error.VALIDATION_ERROR, description=description)
        return Response(status=400, body=error.SerializeToString())


@middleware
async def handle_application_errors(req: Request, handler) -> Response:
    try:
        return await handler(req)
    except RecipientDenied:
        error = Error(code=Error.RECIPIENT_DENIED)
        return Response(status=406, body=error.SerializeToString())
    except UnsupportedTransport as exc:
        error = Error(
            code=Error.UNSUPPORTED_TRANSPORT,
            description=f"notification_type={exc.notification_type.value}, "
            f"unsupported_transports={','.join(map(attrgetter('value'), exc.transports))}",  # noqa
        )
        return Response(status=400, body=error.SerializeToString())
    except NoOrgsForBizId as exc:
        error = Error(code=Error.NO_ORGS_FOR_BIZ_ID, description=_list_to_str(exc.args))
        return Response(status=400, body=error.SerializeToString())
    except ClientNotFoundInDoorman as exc:
        error = Error(code=Error.UNKNOWN_CLIENT, description=_list_to_str(exc.args))
        return Response(status=400, body=error.SerializeToString())
    except NoOrginfo as exc:
        error = Error(code=Error.NO_ORG_INFO, description=_list_to_str(exc.args))
        return Response(status=400, body=error.SerializeToString())
    except (BvmUnknownBizId, TunerUnknownBizId) as exc:
        error = Error(code=Error.UNKNOWN_BIZ_ID, description=_list_to_str(exc.args))
        return Response(status=400, body=error.SerializeToString())


def _dict_to_str(data: dict, separator=": ") -> str:
    return ", ".join(f"{field}{separator}{data[field]}" for field in sorted(data))


def _list_to_str(data: list) -> Optional[str]:
    return ", ".join([str(e) for e in data]) if data else None
