import asyncio
from contextlib import asynccontextmanager
from typing import Any, AsyncGenerator

from chitai.services.book import BookService
from chitai.services.consume import ConsumeDirectoryWatcher
from litestar import Litestar, get
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import SwaggerRenderPlugin
from litestar.di import Provide
from litestar.security.jwt import OAuth2PasswordBearerAuth, Token
from litestar.connection import ASGIConnection
from litestar.static_files import create_static_files_router
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession


from chitai import controllers as c
from chitai.cli import CalibreCLIPlugin
from chitai.config import settings
from chitai.database.config import alchemy
from chitai.database.models.user import User
from chitai.schemas.library import LibraryCreate
from chitai.services.dependencies import provide_user, provide_user_service
from chitai.services.library import LibraryService
from chitai.services.utils import create_directory
from chitai.exceptions.handlers import exception_handlers


@get("/")
async def index() -> str:
    return "Hello from Chitai!"


@get("/healthcheck")
async def healthcheck(db_session: AsyncSession) -> dict[str, str]:
    await db_session.execute(text("SELECT 1"))
    return {"status": "OK"}


async def retrieve_user_handler(
    token: Token, connection: ASGIConnection[Any, Any, Any, Any]
) -> User | None:
    service = await anext(
        provide_user_service(
            settings.alchemy_config.provide_session(
                connection.app.state, connection.scope
            )
        )
    )
    return await service.get_one_or_none(email=token.sub)


oauth2_auth = OAuth2PasswordBearerAuth[User](
    retrieve_user_handler=retrieve_user_handler,
    token_secret=settings.token_secret,
    # we are specifying the URL for retrieving a JWT access token
    token_url="/access/login",
    # we are specifying which endpoints should be excluded from authentication. In this case the login endpoint
    # and our openAPI docs.
    exclude=[
        "/healthcheck",
        "/access/login",
        "/access/login/token",
        "/access/signup",
        "/opds",
        "/schema",
        "/syncs",
        "/users/auth",
    ],
)

watcher_task: asyncio.Task


@asynccontextmanager
async def setup_db_connection(app: Litestar) -> AsyncGenerator[None, None]:
    # Setup databse
    async with settings.alchemy_config.get_session() as db_session:
        # Create default library if none exist
        library_service = LibraryService(session=db_session)
        _, total = await library_service.list_and_count()

        if total == 0:
            await library_service.create(
                LibraryCreate(
                    name=settings.default_library_name,
                    root_path=settings.default_library_path,
                )
            )
            await db_session.commit()

        try:
            yield
        finally:
            await db_session.aclose()


@asynccontextmanager
async def setup_directory_watcher(app: Litestar) -> AsyncGenerator[None, None]:

    # Create book covers directory if it does not exist
    await create_directory(settings.book_cover_path)
    # Create consume directory
    await create_directory(settings.consume_path)

    async with settings.alchemy_config.get_session() as db_session:
        book_service = BookService(session=db_session)
        library_service = LibraryService(session=db_session)

        file_watcher = ConsumeDirectoryWatcher(
            settings.consume_path, library_service, book_service
        )
        watcher_task = asyncio.create_task(file_watcher.init_watcher())

        try:
            yield
        finally:
            watcher_task.cancel()


def create_app() -> Litestar:
    return Litestar(
        route_handlers=[
            c.BookController,
            c.LibraryController,
            c.AccessController,
            c.BookshelfController,
            c.AuthorController,
            c.PublisherController,
            c.TagController,
            c.OpdsController,
            c.DeviceController,
            c.KosyncController,
            create_static_files_router(path="/covers", directories=["./covers"]),
            index,
            healthcheck,
        ],
        exception_handlers=exception_handlers,
        lifespan=[setup_db_connection, setup_directory_watcher],
        plugins=[alchemy, CalibreCLIPlugin()],
        on_app_init=[oauth2_auth.on_app_init],
        openapi_config=OpenAPIConfig(
            title="Chitai",
            description="Chitai API docs",
            version="0.0.1",
            render_plugins=[SwaggerRenderPlugin()],
        ),
        dependencies={"current_user": Provide(provide_user, sync_to_thread=False)},
        debug=settings.api_debug,
    )


app = create_app()
