#  Copyright (C) 2021
#  ABM, Moscow
#
#  UNPUBLISHED PROPRIETARY MATERIAL.
#  ALL RIGHTS RESERVED.
#
#  Authors: Mike Orlov <m.orlov@abm-jsc.ru>
#
import dataclasses
import logging
from typing import Awaitable, Callable, Union, Optional

import aiohttp
from aiohttp import web
from aiohttp.web_urldispatcher import UrlDispatcher

from .charset import Charset
from .http_status_codes import HttpStatusCode
from .answer import ErrorAnswer, ExceptionAnswer, JsonableAnswer

logger = logging.getLogger(__name__)

Handler = Callable[[web.Request], Awaitable[web.Response]]
WsHandler = Callable[[web.Request], Awaitable[web.WebSocketResponse]]


class NotFoundErrorAnswer(JsonableAnswer):
    @dataclasses.dataclass
    class Payload:
        error: str
        requested_path: str
        registered_paths: list[str]
        done: bool = False

    def __init__(self, error: str, requested_path: str, registered_paths: list[str], status: HttpStatusCode,
                 headers: Optional[dict[str, str]] = None, charset: Charset = Charset.UTF8) -> None:
        super().__init__(payload=self.Payload(error, requested_path, registered_paths),
                         status=status, headers=headers, charset=charset)


class NotFoundApplicationErrorAnswer(JsonableAnswer):
    @dataclasses.dataclass
    class Payload:
        error: str
        done: bool = False

    def __init__(self, error: str, status: HttpStatusCode,
                 headers: Optional[dict[str, str]] = None, charset: Charset = Charset.UTF8) -> None:
        super().__init__(payload=self.Payload(error),
                         status=status, headers=headers, charset=charset)


@web.middleware
async def error_middleware(
        request: web.Request, handler: aiohttp.typedefs.Handler, router: UrlDispatcher
) -> web.StreamResponse | Awaitable[web.StreamResponse]:
    try:
        response = await handler(request)
    except web.HTTPException as e:
        logger.warning(f"http exception in handler for {request.method} {request.path}, exception: {e}")
        status = HttpStatusCode(e.status)
        error = e.reason
        if e.status == HttpStatusCode.NotFound or e.status == HttpStatusCode.MethodNotAllowed:
            registered_paths = [res.canonical for res in router.resources()][1:]
            if request.path in registered_paths:
                response = NotFoundApplicationErrorAnswer(error, status=status)
            else:
                response = NotFoundErrorAnswer(error, status=status, requested_path=request.path,
                                               registered_paths=registered_paths
                                               )
        else:
            response = ErrorAnswer(error, status=status)
    except Exception as e:
        logger.exception(e)
        response = ExceptionAnswer(exception=e, status=HttpStatusCode.InternalServerError)

    if not isinstance(response, web.Response) and not isinstance(response, web.WebSocketResponse):
        logger.error("Response is not instance of class Response!")
        response = ErrorAnswer(
            error=f"Response is not instance of class Response",
            status=HttpStatusCode.InternalServerError
        )

    return response
