import contextlib
import os
import uuid
from typing import Annotated
from typing import List
from typing import Optional

from fastapi import APIRouter
from fastapi import Depends
from fastapi import Form
from fastapi import HTTPException
from fastapi import Query
from fastapi import Request
from fastapi.responses import HTMLResponse
from fastapi.responses import RedirectResponse
from fastapi_users import BaseUserManager
from fastapi_users import FastAPIUsers
from fastapi_users import schemas
from fastapi_users import UUIDIDMixin
from fastapi_users.authentication import AuthenticationBackend
from fastapi_users.authentication import BearerTransport
from fastapi_users.authentication import CookieTransport
from fastapi_users.authentication import JWTStrategy
from fastapi_users.exceptions import UserAlreadyExists
from fastapi_users_db_sqlmodel import SQLModelUserDatabase
from sqlalchemy import asc
from sqlalchemy import desc
from sqlalchemy import func
from sqlalchemy import inspect
from sqlalchemy import or_
from sqlalchemy import select
from sqlalchemy import Text
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import AutoString

from ghostforge.db import get_session
from ghostforge.db import get_user_db
from ghostforge.db import User
from ghostforge.htmljson import HtmlJson


SECRET = os.environ.get("GHOSTFORGE_JWT_SECRET")


async def get_user_manager(user_db: SQLModelUserDatabase = Depends(get_user_db)):
    yield UserManager(user_db)


get_async_session_context = contextlib.asynccontextmanager(get_session)
get_user_db_context = contextlib.asynccontextmanager(get_user_db)
get_user_manager_context = contextlib.asynccontextmanager(get_user_manager)

gf = APIRouter()
hj = HtmlJson()


class UserRead(schemas.BaseUser[uuid.UUID]):
    username: str


class UserCreate(schemas.BaseUserCreate):
    username: str
    email: str


class UserUpdate(schemas.BaseUserUpdate):
    username: str
    email: str


class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
    reset_password_token_secret = SECRET
    verification_token_secret = SECRET

    async def on_after_register(self, user: User, request: Optional[Request] = None):
        print(f"User {user.id} has registered.")

    async def on_after_forgot_password(self, user: User, token: str, request: Optional[Request] = None):
        print(f"User {user.id} has forgot their password. Reset token: {token}")

    async def on_after_request_verify(self, user: User, token: str, request: Optional[Request] = None):
        print(f"Verification requested for user {user.id}. Verification token: {token}")


bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
cookie_transport = CookieTransport(cookie_httponly=True, cookie_name="ghostforge", cookie_samesite="strict")


def get_jwt_strategy() -> JWTStrategy:
    return JWTStrategy(secret=SECRET, lifetime_seconds=604800)


jwt_backend = AuthenticationBackend(
    name="jwt",
    transport=bearer_transport,
    get_strategy=get_jwt_strategy,
)

web_backend = AuthenticationBackend(name="cookie", transport=cookie_transport, get_strategy=get_jwt_strategy)

fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [web_backend, jwt_backend])


def get_current_user(active: bool = True, optional: bool = False) -> User:
    return fastapi_users.current_user(active=active, optional=optional)


@gf.get("/manage", response_model=List[UserRead])
@hj.html_or_json("users/users.html")
async def read_users(
    current_user: Annotated[User, Depends(get_current_user())],
    offset: int = 0,
    limit: int = Query(default=100, lte=100),
    session: AsyncSession = Depends(get_session),
    request: Request = None,
):
    query = await session.execute(select(User).offset(offset).limit(limit))
    users = query.scalars().all()
    data = {"users": users, "user": current_user, "crumbs": [("settings", False), ("users", False)]}
    request.state.ghostforge = data | getattr(request.state, "ghostforge", {})
    return users


@gf.post("/manage/new", response_model=UserRead)
async def create_user(
    user: UserCreate,
    current_user: Annotated[User, Depends(get_current_user())],
    session: AsyncSession = Depends(get_session),
):
    try:
        async with get_async_session_context() as session:
            async with get_user_db_context(session) as user_db:
                async with get_user_manager_context(user_db) as user_manager:
                    if not current_user.is_superuser:
                        user.is_superuser = False
                    user = await user_manager.create(
                        UserCreate(
                            email=user.email,
                            username=user.username,
                            password=user.password,
                            is_superuser=user.is_superuser,
                        )
                    )
                    return UserRead(user)
    except UserAlreadyExists:
        return {"error": "User exists"}


@gf.get("/manage/{user_guid}", response_model=UserRead)
@hj.html_or_json("users/user.html")
async def get_user(
    user_guid: str,
    current_user: Annotated[User, Depends(get_current_user())],
    user_manager: UserManager = Depends(get_user_manager),
    session: AsyncSession = Depends(get_session),
    request: Request = None,
):
    async with get_user_db_context(session) as user_db:
        async with get_user_manager_context(user_db) as user_manager:
            user = await user_manager.get(user_guid)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    data = {
        "target_user": user,
        "user": current_user,
        "crumbs": [("settings", False), ("users", "/manage"), (user.id, False)],
    }
    request.state.ghostforge = data | getattr(request.state, "ghostforge", {})
    return user


@gf.put("/manage/{user_guid}", response_model=UserRead)
async def update_user(
    user_guid: uuid.UUID,
    user_update: UserUpdate,
    current_user: Annotated[User, Depends(get_current_user())],
    user_manager: UserManager = Depends(get_user_manager),
    session: AsyncSession = Depends(get_session),
):
    if not current_user.is_superuser and current_user.id != user_guid:
        raise HTTPException(status_code=403, detail="No permission to edit this user")
    db_user = await user_manager.get(user_guid)
    if not db_user:
        raise HTTPException(status_code=404, detail="User not found")

    user_update = UserUpdate(**user_update.dict(exclude_unset=True))
    return await user_manager.update(user_update, db_user)


@gf.get("/profile", response_class=HTMLResponse)
async def home(request: Request, current_user=Depends(get_current_user())):
    return RedirectResponse(url=f"/manage/{current_user.id}")


@gf.post("/manage")
async def get_users(
    current_user: Annotated[User, Depends(get_current_user())],
    start: int = Form(None, alias="start"),
    length: int = Form(None, alias="length"),
    search: Optional[str] = Form(None, alias="search[value]"),
    order_col: int = Form(0, alias="order[0][column]"),
    order_dir: str = Form("asc", alias="order[0][dir]"),
    session: AsyncSession = Depends(get_session),
    request: Request = None,
):
    offset = start
    limit = offset + length

    # Get the column name based on the order_col integer value
    user_columns = [c.key for c in inspect(User).c]
    order_col_name = user_columns[order_col]

    # Retrieve filtered users from database
    count = select(func.count(User.id))
    total_users = (await session.execute(count)).scalar()

    query = select(User).offset(offset).limit(limit)

    if search:
        conditions = []
        for col in user_columns:
            column_attr = getattr(User, col)
            if isinstance(column_attr.type, (AutoString, Text)):
                conditions.append(column_attr.ilike(f"%{search}%"))
        query = query.where(or_(*conditions))
    if order_dir == "asc":
        query = query.order_by(asc(getattr(User, order_col_name)))
    else:
        query = query.order_by(desc(getattr(User, order_col_name)))
    users = (await session.execute(query)).all()

    return {
        "recordsTotal": total_users,
        "recordsFiltered": total_users,
        "data": users,
    }
