from __future__ import annotations

import secrets
from typing import Annotated, Any

from sayer import Option, command

import edgy
from edgy.conf import settings


@command
def admin_serve(
    port: Annotated[
        int,
        Option(
            8000,
            "-p",
            help="Port to run the development server.",
            show_default=True,
        ),
    ],
    host: Annotated[
        str,
        Option(
            "localhost",
            help="Server host. Typically localhost.",
            show_default=True,
        ),
    ],
    debug: Annotated[
        bool,
        Option(
            False,
            help="Start the application in debug mode.",
            is_flag=True,
        ),
    ],
    create_all: Annotated[
        bool,
        Option(
            False,
            help="Create all models when not existent.",
            is_flag=True,
        ),
    ],
    log_level: Annotated[
        str,
        Option(
            "info",
            help="What log level should uvicorn run.",
            show_default=True,
        ),
    ],
    auth_name: Annotated[
        str,
        Option(
            "admin",
            help="Basic auth name.",
            show_default=True,
        ),
    ],
    auth_pw: Annotated[
        str | None,
        Option(
            None,
            help="Basic auth password. By default autogenerated.",
            show_default=False,
        ),
    ],
    admin_prefix_url: Annotated[
        str | None,
        Option(
            None,
            help="Overwrites the admin prefix url in the settings.",
            show_default=False,
        ),
    ],
    admin_path: Annotated[
        str | None,
        Option(
            None,
            help='The sub-path under which the admin is available. Defaults to "/admin" if unset in settings',
            show_default=False,
        ),
    ],
) -> None:
    """Starts the Edgy db admin server.

    The --app can be passed in the form of <module>.<submodule> or be set
    as environment variable EDGY_DEFAULT_APP.

    Alternatively, if none is passed, Edgy will perform the application discovery.

    How to run: `edgy admin_serve`
    """
    try:
        from lilya.apps import Lilya
        from lilya.middleware import DefineMiddleware
        from lilya.middleware.sessions import SessionMiddleware
        from lilya.permissions import DefinePermission
        from lilya.routing import Include
    except ImportError:
        raise RuntimeError("Lilya needs to be installed to run admin_serve.") from None
    try:
        import jinja2  # noqa
    except ImportError:
        raise RuntimeError("Jinja2 needs to be installed to run admin_serve.") from None
    try:
        import uvicorn
    except ImportError:
        raise RuntimeError("Uvicorn needs to be installed to run admin_serve.") from None
    from edgy.contrib.admin.permissions import BasicAuthAccess

    old_instance = edgy.monkay.instance
    if admin_prefix_url is not None:
        edgy.monkay.settings.admin_config.admin_prefix_url = admin_prefix_url
    if admin_path is None:
        admin_path = edgy.monkay.settings.admin_config.admin_prefix_url

    if admin_path is None:
        admin_path = "/admin"

    if old_instance is None:
        raise RuntimeError(
            "Auto-detection of app failed.\n"
            'You need to specify an app which registry is used. For experimenting use: --app="tests.cli.main"'
        )
    from edgy.contrib.admin.application import create_admin_app
    from edgy.contrib.admin.controllers import admin_not_found

    print_pw = False
    if auth_pw is None:
        auth_pw = secrets.token_urlsafe()
        print_pw = True
    admin_app = create_admin_app()
    admin_middleware = [
        DefineMiddleware(
            SessionMiddleware,
            secret_key=settings.admin_config.SECRET_KEY,
            session_cookie="admin_session",
        ),
    ]

    routes = [
        Include(
            path=admin_path,
            app=admin_app,
            permissions=[
                DefinePermission(
                    BasicAuthAccess,
                    print_pw=print_pw,
                    username=auth_name,
                    password=auth_pw,
                )
            ],
            middleware=admin_middleware,
        ),
    ]
    if old_instance.app is not None:
        routes.append(Include(path="/", app=old_instance.app))
    app: Any = Lilya(
        routes=routes,
        # Register the custom 404 not found handler.
        exception_handlers={404: admin_not_found},
    )
    if debug:
        app.debug = True
        admin_app.debug = True
    app = old_instance.registry.asgi(app)
    if create_all:
        edgy.run_sync(old_instance.registry.create_all())
    edgy.monkay.set_instance(edgy.Instance(registry=old_instance.registry, app=app))

    uvicorn.run(
        app=app,
        port=port,
        host=host,
        reload=False,
        lifespan="on",
        log_level=log_level,
    )
