import argparse
import asyncio
import logging
import os
import sys
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from http import HTTPStatus
from pathlib import Path
from typing import Final

import firebase_admin  # type: ignore[import-untyped]
import uvicorn
from dishka import AsyncContainer
from dishka.integrations.fastapi import setup_dishka
from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from firebase_admin import credentials
from prometheus_fastapi_instrumentator import Instrumentator

from template_project.application.auth_identity.errors import AuthError
from template_project.web_api.configuration import Configuration, load_configuration
from template_project.web_api.ioc.make import make_ioc
from template_project.web_api.routes import auth, healthcheck, key_skills, notification, profile, resume, storage

LOG_CONFIG: Final = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "default": {
            "format": "%(asctime)s [%(levelname)s] [%(name)s] %(message)s",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "default",
        },
    },
    "root": {
        "level": "DEBUG",
        "handlers": ["console"],
    },
}


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    configuration: Configuration = await app.state.dishka_container.get(Configuration)
    cred = credentials.Certificate(configuration.firebase.certificate_path)
    firebase_admin.initialize_app(cred)
    yield
    await app.state.dishka_container.close()


async def auth_error_exception_handler(
    request: Request,
    exception: AuthError,
) -> Response:
    return Response(status_code=HTTPStatus.UNAUTHORIZED, content="Authentication failed")


def make_asgi_application(
    ioc: AsyncContainer,
) -> FastAPI:
    app = FastAPI(
        lifespan=lifespan,
        docs_url="/docs",
        title="Recomenci Fluon",
        description="Recomenci Fluon API",
        version="1.0.0",
        openapi_url="/openapi.json",
    )
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )
    app.add_exception_handler(AuthError, auth_error_exception_handler)  # type: ignore[arg-type]
    app.include_router(auth.router)
    app.include_router(healthcheck.router)
    app.include_router(profile.router)
    app.include_router(key_skills.router)
    app.include_router(notification.router)
    app.include_router(storage.router)
    app.include_router(resume.router)
    Instrumentator().instrument(app).expose(app)

    setup_dishka(container=ioc, app=app)

    return app


def make_server(configuration: Configuration) -> uvicorn.Server:
    ioc = make_ioc(configuration)
    asgi_application = make_asgi_application(ioc)

    config = uvicorn.Config(
        app=asgi_application,
        host=configuration.server.host,
        port=configuration.server.port,
        log_config=LOG_CONFIG,
        access_log=configuration.server.access_log,
    )
    return uvicorn.Server(config)


async def run_server(
    configuration_path: Path,
) -> None:
    logging.getLogger("httpx").setLevel(logging.WARNING)
    logging.getLogger("httpcore").setLevel(logging.WARNING)

    configuration = load_configuration(configuration_path)
    server = make_server(configuration)
    await server.serve()


def main() -> None:
    if sys.platform == "win32":
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

    arg_parser = argparse.ArgumentParser()
    arg_parser.add_argument("configuration", default=None)

    args = arg_parser.parse_args()
    configuration_path = args.configuration or os.getenv("CONFIGURATION_PATH")

    if configuration_path is None:
        raise RuntimeError(
            "pass the path to the config or specify it in the environment variables `CONFIGURATION_PATH`",
        )

    asyncio.run(run_server(Path(configuration_path)))


if __name__ == "__main__":
    main()
