import json
import logging

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

from maps_adv.statistics.dashboard.proto import error_pb2
from maps_adv.statistics.dashboard.server.lib.api_provider import ApiProvider
from maps_adv.statistics.dashboard.server.lib.data_manager import (
    NoCampaignsPassed,
    NothingFound,
)

__all__ = ["create"]

logger = logging.getLogger(__name__)


def create(provider: ApiProvider) -> web.Application:
    api = web.Application(
        middlewares=[
            _handle_nothing_found,
            _handle_validation_error,
            _handle_no_campaigns_passed_exception,
        ]
    )

    resources = Resources(provider)

    api.add_routes(
        [
            web.get("/ping", lambda _: Response(status=200)),
            web.post(
                "/statistics/campaigns/", resources.calculate_by_campaigns_and_period
            ),
            web.post(
                "/statistics/campaigns/charged_sum/",
                resources.calculate_campaigns_charged_sum,
            ),
            web.post(
                "/statistics/campaigns/events/",
                resources.calculate_campaigns_events_on_date,
            ),
            web.post(
                "/statistics/campaigns/icons/", resources.fetch_search_icons_statistics
            ),
            web.get("/monitorings/", resources.calculate_monitoring_data),  # deprecated
            web.get("/monitoring/", resources.calculate_monitoring_data),
            web.get(  # deprecated
                "/monitoring/queries", resources.calculate_monitoring_data_for_queries
            ),
            web.get(
                "/monitoring/queries/", resources.calculate_monitoring_data_for_queries
            ),
        ]
    )

    return api


class Resources:
    __slots__ = ("_provider",)

    _provider: ApiProvider

    def __init__(self, provider: ApiProvider):
        self._provider = provider

    async def calculate_by_campaigns_and_period(self, req: Request) -> Response:
        raw_data = await req.read()
        got = await self._provider.calculate_by_campaigns_and_period(raw_data)
        return Response(status=200, body=got)

    async def calculate_campaigns_charged_sum(self, req: Request) -> Response:
        raw_data = await req.read()
        got = await self._provider.calculate_campaigns_charged_sum(raw_data)
        return Response(status=200, body=got)

    async def fetch_search_icons_statistics(self, request: Request) -> Response:
        body = await request.read()
        result = await self._provider.fetch_search_icons_statistics(body)
        return Response(body=result, status=200)

    async def calculate_campaigns_events_on_date(self, request: Request) -> Response:
        body = await request.read()
        result = await self._provider.calculate_campaigns_events_for_period(body)
        return Response(body=result, status=200)

    async def calculate_monitoring_data(self, request: Request) -> Response:
        """
        Solomon calls this with timestamp args which look like this:
        http://<HOST>:<Port><URL Path>?now=2017-06-11T00:45:45.542Z&period=15s
        it means we should provide data for period (now-preiod, now]
        https://wiki.yandex-team.ru/solomon/howtostart/
        """
        now = request.rel_url.query.get("now")
        period = request.rel_url.query.get("period")

        if not now or not period:
            return Response(status=400)  # Bad request

        result = await self._provider.calculate_monitoring_data(now, period)
        return Response(body=json.dumps(result), status=200)

    async def calculate_monitoring_data_for_queries(self, request: Request) -> Response:
        """
        Solomon calls this with timestamp args which look like this:
        http://<HOST>:<Port><URL Path>?now=2017-06-11T00:45:45.542Z&period=15s
        it means we should provide data for period (now-preiod, now]
        https://wiki.yandex-team.ru/solomon/howtostart/
        """
        now = request.rel_url.query.get("now")
        period = request.rel_url.query.get("period")

        if not now or not period:
            return Response(status=400)  # Bad request

        result = await self._provider.calculate_monitoring_data_for_queries(now, period)
        return Response(body=json.dumps(result), status=200)


@middleware
async def _handle_nothing_found(request: Request, handler) -> Response:
    try:
        return await handler(request)
    except NothingFound:
        return Response(status=204)


@web.middleware
async def _handle_no_campaigns_passed_exception(request, handler):
    try:
        return await handler(request)
    except NoCampaignsPassed as exc:
        logger.exception(exc)
        error = error_pb2.Error(code=error_pb2.Error.NO_CAMPAIGNS_PASSED)
        return web.Response(status=400, body=error.SerializeToString())


@middleware
async def _handle_validation_error(request: Request, handler) -> Response:
    try:
        return await handler(request)
    except ValidationError as exc:
        logger.error("Serialization error: %s", exc.normalized_messages())

        error = error_pb2.Error(
            code=error_pb2.Error.VALIDATION_ERROR, description=json.dumps(exc.messages)
        )
        return web.Response(status=400, body=error.SerializeToString())
