#
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_jinja2
import logging

from aiohttp.typedefs import Middleware
from aiohttp.web import (
    HTTPClientError,
    HTTPException,
    HTTPMethodNotAllowed,
    HTTPNoContent,
    HTTPServerError,
    HTTPUnauthorized,
    Request,
    StreamResponse,
    json_response,
    middleware,
)

from ahriman.web.middlewares import HandlerType


__all__ = ["exception_handler"]


def _is_templated_unauthorized(request: Request) -> bool:
    """
    check if the request is eligible for rendering HTML template

    Args:
        request(Request): source request to check

    Returns:
        bool: ``True`` in case if response should be rendered as HTML and ``False`` otherwise
    """
    return request.path in ("/api/v1/login", "/api/v1/logout") \
        and "application/json" not in request.headers.getall("accept", [])


def exception_handler(logger: logging.Logger) -> Middleware:
    """
    exception handler middleware. Just log any exception (except for client ones)

    Args:
        logger(logging.Logger): class logger

    Returns:
        Middleware: built middleware

    Raises:
        HTTPNoContent: OPTIONS method response
    """
    @middleware
    async def handle(request: Request, handler: HandlerType) -> StreamResponse:
        try:
            return await handler(request)
        except HTTPUnauthorized as ex:
            if _is_templated_unauthorized(request):
                context = {"code": ex.status_code, "reason": ex.reason}
                return aiohttp_jinja2.render_template("error.jinja2", request, context, status=ex.status_code)
            return json_response(data={"error": ex.reason}, status=ex.status_code)
        except HTTPMethodNotAllowed as ex:
            if ex.method == "OPTIONS":
                # automatically handle OPTIONS method, idea comes from
                # https://github.com/arcan1s/ffxivbis/blob/master/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala#L32
                raise HTTPNoContent(headers={"Allow": ",".join(sorted(ex.allowed_methods))})
            if ex.method == "HEAD":
                # since we have special autogenerated HEAD method, we need to remove it from list of available
                ex.allowed_methods = {method for method in ex.allowed_methods if method != "HEAD"}
                ex.headers["Allow"] = ",".join(sorted(ex.allowed_methods))
                raise ex
            raise
        except HTTPClientError as ex:
            return json_response(data={"error": ex.reason}, status=ex.status_code)
        except HTTPServerError as ex:
            logger.exception("server exception during performing request to %s", request.path)
            return json_response(data={"error": ex.reason}, status=ex.status_code)
        except HTTPException:  # just raise 2xx and 3xx codes
            raise
        except Exception as ex:
            logger.exception("unknown exception during performing request to %s", request.path)
            return json_response(data={"error": str(ex)}, status=500)

    return handle
