import logging
from logging.config import dictConfig

from sentry_sdk.integrations.asgi import SentryAsgiMiddleware

from fastapi import FastAPI, applications
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.responses import JSONResponse
from fastapi.routing import APIRoute

from intranet.library.fastapi_csrf.src import collect_csrf_exempt, CsrfMiddleware

from intranet.trip.lib.asgi_log_middleware import log_context_middleware

from intranet.trip.src.middlewares import get_auth_middleware_class
from intranet.trip.src.config import settings
from intranet.trip.src.logging import LOGGING_CONFIG
from intranet.trip.src.router import internal_api_router, external_api_router
from intranet.trip.src.db import init_pg, close_pg
from intranet.trip.src.db.gateways import RecordNotFound, OperationError
from intranet.trip.src.error_booster import init_error_booster
from intranet.trip.src.exceptions import WorkflowError, PermissionDenied
from intranet.trip.src.lib.aeroclub.exceptions import AeroclubError, AeroclubClientError
from intranet.trip.src.lib.aviacenter.exceptions import AviaCenterError
from intranet.trip.src.lib.ok.exceptions import OkError
from intranet.trip.src.lib.travel.exceptions import TravelError
from intranet.trip.src.redis import create_redis_pool


dictConfig(LOGGING_CONFIG)

logger = logging.getLogger(__name__)


def swagger_monkey_patch(*args, **kwargs):
    """
    Фиксируем версию статики swagger-ui, пока они не починят баг
    https://github.com/tiangolo/fastapi/issues/1762
    """
    return get_swagger_ui_html(
        *args, **kwargs,
        swagger_js_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3.29/swagger-ui-bundle.js",
        swagger_css_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3.29/swagger-ui.css")


applications.get_swagger_ui_html = swagger_monkey_patch


def generate_operation_ids(app):
    for route in app.routes:
        if isinstance(route, APIRoute):
            name = route.name
            if route.path.startswith('/external/'):
                name = f'external_{name}'
            route.operation_id = name


def bind_events(app):
    async def startup():
        await init_pg(app)

    async def shutdown():
        await close_pg(app)

    app.on_event('startup')(startup)

    @app.on_event('startup')
    async def create_arq():
        app.state.redis = await create_redis_pool()

    @app.on_event('startup')
    async def collect_csrf_exempt_endpoints():
        await collect_csrf_exempt(app)

    @app.on_event('shutdown')
    async def close_arq():
        app.state.redis.close()
        await app.state.redis.wait_closed()

    app.on_event('shutdown')(shutdown)


def bind_middlewares(app):
    """
    add_middleware добавляет middleware в начало списка,
    поэтому выполняться они будут в обратном порядке.
    """
    app.middleware('http')(log_context_middleware)

    if settings.ENABLE_CSRF_PROTECTION:
        app.add_middleware(
            CsrfMiddleware,
            secret_key=settings.SECRET_KEY,
        )

    app.add_middleware(get_auth_middleware_class())
    app.add_middleware(GZipMiddleware, minimum_size=1000)

    if settings.ENABLE_ERROR_BOOSTER:
        init_error_booster()
        logger.info('adding sentry middleware')
        app.add_middleware(SentryAsgiMiddleware)


def bind_exception_handlers(app):
    async def record_not_found_exception_handler(request, exc):
        return JSONResponse(
            status_code=404,
            content={'detail': str(exc)},
        )

    async def http_422_exception_handler(request, exc):
        return JSONResponse(
            status_code=422,
            content={'detail': str(exc)},
        )

    async def permission_denied_handler(request, exc):
        return JSONResponse(
            status_code=exc.status_code,
            content={'detail': exc.detail},
        )

    async def external_request_exception_handler(request, exc):
        return JSONResponse(
            status_code=exc.status_code,
            content={'detail': exc.content},
        )

    async def unhandled_exception_handler(request, exc):
        message = str(exc) if settings.ENV_TYPE != 'production' else 'Unhandled exception'
        return JSONResponse(
            status_code=500,
            content={'detail': message}
        )

    app.exception_handler(RecordNotFound)(record_not_found_exception_handler)

    for exc in [OperationError, WorkflowError, AeroclubError]:
        app.exception_handler(exc)(http_422_exception_handler)

    app.exception_handler(AeroclubClientError)(external_request_exception_handler)
    app.exception_handler(AviaCenterError)(external_request_exception_handler)
    app.exception_handler(TravelError)(external_request_exception_handler)
    app.exception_handler(OkError)(external_request_exception_handler)

    app.exception_handler(PermissionDenied)(permission_denied_handler)
    app.exception_handler(Exception)(unhandled_exception_handler)


def get_app():
    app = FastAPI(
        title='trip',
        openapi_url=settings.OPENAPI_URL,
        docs_url='/api/docs',
        redoc_url='/api/redoc',
    )
    app.include_router(internal_api_router, prefix='/api')
    app.include_router(external_api_router, prefix='/external')
    generate_operation_ids(app)

    bind_events(app)
    bind_middlewares(app)
    bind_exception_handlers(app)
    return app


app = get_app()
