"""
The Brood HTTP API
"""
import logging
from typing import Any, Dict, List, Optional
import uuid

from fastapi import (
    BackgroundTasks,
    Depends,
    FastAPI,
    Form,
    HTTPException,
    Path,
    Query,
    Request,
    Response,
    status,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2PasswordRequestForm
import stripe  # type: ignore

from . import actions
from . import data
from . import exceptions
from . import subscriptions
from . import models
from .middleware import (
    oauth2_scheme,
    autogenerated_user_token_check,
    get_current_user,
    is_token_restricted,
    is_token_restricted_or_installation,
    get_current_user_or_installation,
)
from .external import yield_db_session_from_env
from .version import BROOD_VERSION
from .settings import (
    group_invite_link_from_env,
    ORIGINS,
    STRIPE_SIGNING_SECRET,
    REQUIRE_EMAIL_VERIFICATION,
    SEND_EMAIL_WELCOME,
    DOCS_TARGET_PATH,
)
from .resources.api import app as resources_api

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

tags_metadata = [
    {"name": "users", "description": "Operations with users."},
    {"name": "tokens", "description": "Operations with user tokens."},
    {"name": "groups", "description": "Operations with groups."},
    {"name": "subscriptions", "description": "Operations with group subscriptions."},
    {"name": "applications", "description": "Operations with resource applications"},
]

app = FastAPI(
    title=f"Bugout authentication & authorization module",
    description="Auth API endpoints to work with users, tokens and groups.",
    version=BROOD_VERSION,
    openapi_tags=tags_metadata,
    openapi_url="/openapi.json",
    docs_url=None,
    redoc_url=f"/{DOCS_TARGET_PATH}",
)

# CORS settings
app.add_middleware(
    CORSMiddleware,
    allow_origins=ORIGINS,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.mount("/resources", resources_api)


@app.get("/ping", response_model=data.PingResponse)
async def ping() -> data.PingResponse:
    return data.PingResponse(status="ok")


@app.get("/version", response_model=data.VersionResponse)
async def version() -> data.VersionResponse:
    return data.VersionResponse(version=BROOD_VERSION)


@app.post("/user", tags=["users"], response_model=data.UserResponse)
async def create_user_handler(
    request: Request,
    background_tasks: BackgroundTasks,
    username: str = Form(...),
    email: str = Form(...),
    password: str = Form(...),
    first_name: Optional[str] = Form(None),
    last_name: Optional[str] = Form(None),
    application_id: Optional[uuid.UUID] = Form(None),
    db_session=Depends(yield_db_session_from_env),
) -> data.UserResponse:
    """
    Create new user.

    - **username** (string): Username
    - **email** (string): New user email
    - **password** (string): New user password
    - **first_name** (string, null): User first name
    - **last_name** (string, null): User last name
    - **application_id** (uuid, null): External application user belongs to
    """
    # If correct BOT_INSTALLATION_TOKEN_HEADER is provided with request it triggers
    # autogenerated user creation.
    autogenerated_user = autogenerated_user_token_check(request)

    try:
        user = actions.create_user(
            db_session,
            username=username,
            email=email,
            password=password,
            autogenerated_user=autogenerated_user,
            first_name=first_name,
            last_name=last_name,
            application_id=application_id,
        )
    except actions.UserAlreadyExists:
        raise HTTPException(
            status_code=409,
            detail="There are conflict when adding a user to the database",
        )
    except actions.UsernameInvalidParameters:
        raise HTTPException(
            status_code=422, detail="Username must not contain spaces",
        )
    except actions.PasswordInvalidParameters as invalid_password_error:
        raise HTTPException(
            status_code=422, detail=invalid_password_error.generic_error_message,
        )
    except Exception as e:
        logger.error(e)
        raise HTTPException(status_code=500)

    if autogenerated_user:
        return user

    if SEND_EMAIL_WELCOME and application_id is None:
        background_tasks.add_task(
            actions.send_welcome_email, user=user,
        )

    if not REQUIRE_EMAIL_VERIFICATION:
        user.verified = True
        db_session.commit()
        return user

    background_tasks.add_task(
        actions.send_verification_email, session=db_session, user_id=user.id
    )
    return user


@app.post("/token", tags=["tokens"], response_model=data.TokenResponse)
async def create_token_handler(
    form_data: OAuth2PasswordRequestForm = Depends(),
    token_type: Optional[models.TokenType] = Form(models.TokenType.bugout),
    token_note: Optional[str] = Form(None),
    restricted: bool = Form(False),
    application_id: Optional[uuid.UUID] = Form(None),
    db_session=Depends(yield_db_session_from_env),
) -> data.TokenResponse:
    """
    Generates new token.
    By default type is "bugout" and note is "Bugout login token".

    - **username** (string): Username
    - **password** (string): User password
    - **token_type** (string): Token type
    - **token_note** (string, null): Short token description
    - **restricted** (boolean, null): If True, token will be created with restrictions
    """
    try:
        token = actions.login(
            session=db_session,
            username=form_data.username,
            password=form_data.password,
            token_type=token_type,
            token_note=token_note,
            restricted=restricted,
            application_id=application_id,
        )
    except actions.UserNotFound:
        raise HTTPException(status_code=404, detail="No user with that username")
    except actions.UserIncorrectPassword:
        raise HTTPException(status_code=401, detail="Incorrect password")
    return token


@app.post("/token/restricted", tags=["tokens"], response_model=data.TokenResponse)
async def create_token_restricted_handler(
    token_restricted: bool = Depends(is_token_restricted),
    current_user: models.User = Depends(get_current_user),
    token_type: Optional[models.TokenType] = Form(models.TokenType.bugout),
    token_note: Optional[str] = Form("Bugout restricted token"),
    db_session=Depends(yield_db_session_from_env),
) -> data.TokenResponse:
    """
    Generates new restricted token.
    By default type is "bugout" and note is "Bugout restricted token".

    - **token_type** (string): Token type
    - **token_note** (string): Short token description
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to create restricted tokens.",
        )
    try:
        token = actions.create_token(
            session=db_session,
            user_id=current_user.id,
            token_type=token_type,
            token_note=token_note,
            restricted=True,
        )
    except actions.UserNotFound:
        raise HTTPException(status_code=404, detail="No user with that username")
    except actions.UserIncorrectPassword:
        raise HTTPException(status_code=401, detail="Incorrect password")
    return token


@app.delete("/token", tags=["tokens"])
async def delete_token_handler(
    access_token: uuid.UUID = Depends(oauth2_scheme),
    target_token: Optional[uuid.UUID] = Form(None),
    db_session=Depends(yield_db_session_from_env),
) -> uuid.UUID:
    """
    Revoke token and logout user.

    - **target_token** (uuid, null): Token ID to revoke
    """
    try:
        token = actions.revoke_token(
            session=db_session, token=access_token, target=target_token
        )
    except actions.TokenNotFound:
        raise HTTPException(status_code=404, detail="Given token does not exist")
    except exceptions.RestrictedTokenUnauthorized as e:
        raise HTTPException(status_code=403, detail=str(e))
    except exceptions.AccessTokenUnauthorized as e:
        raise HTTPException(status_code=404, detail=str(e))

    return token.id


# TODO(kompotkot): DEPRECATED @app.post("/revoke/{access_token}")
@app.post("/revoke/{access_token}", include_in_schema=False)
@app.delete("/token/{access_token}", tags=["tokens"])
async def delete_token_by_id_handler(
    access_token: uuid.UUID, db_session=Depends(yield_db_session_from_env)
) -> uuid.UUID:
    """
    Revoke token by ID.

    - **access_token** (uuid): Token ID
    """
    try:
        token = actions.revoke_token(session=db_session, token=access_token)
    except actions.TokenNotFound:
        raise HTTPException(status_code=404, detail="Given token does not exist")
    except exceptions.RestrictedTokenUnauthorized as e:
        raise HTTPException(status_code=403, detail=str(e))
    except exceptions.AccessTokenUnauthorized as e:
        raise HTTPException(status_code=404, detail=str(e))

    return token.id


@app.put("/token", tags=["tokens"], response_model=data.TokenResponse)
async def update_token_handler(
    access_token: uuid.UUID = Form(...),
    token_type: Optional[models.TokenType] = Form(None),
    token_note: Optional[str] = Form(None),
    target_token: Optional[uuid.UUID] = Form(None),
    db_session=Depends(yield_db_session_from_env),
) -> data.TokenResponse:
    """
    Update token type and token note for provided token ID.

    - **access_token** (uuid): Token ID
    - **token_type** (string, null): Token type
    - **token_note** (string, null): Short token description
    - **target_token** (uuid, null): Token ID to update
    """
    try:
        token = actions.update_token(
            db_session, access_token, token_type, token_note, target_token
        )
    except actions.TokenNotFound:
        raise HTTPException(status_code=404, detail="Given token does not exist")
    except actions.TokenInvalidParameters:
        raise HTTPException(status_code=400, detail="Invalid token parameters")
    except exceptions.RestrictedTokenUnauthorized as e:
        raise HTTPException(status_code=403, detail=str(e))
    except exceptions.AccessTokenUnauthorized as e:
        raise HTTPException(status_code=404, detail=str(e))

    return token


@app.get("/token/types", tags=["tokens"])
async def get_token_types_handler(
    _: models.User = Depends(get_current_user),
) -> List[str]:
    """
    Get list of all possible token types.
    """
    token_types = [member for member in models.TokenType.__members__]
    return token_types


@app.get("/tokens", tags=["tokens"])
async def get_tokens_handler(
    token_restricted: bool = Depends(is_token_restricted),
    current_user: models.User = Depends(get_current_user),
    active: bool = Query(None),
    token_type: models.TokenType = Query(None),
    restricted: bool = Query(None),
) -> Dict[str, Any]:
    """
    Get list of tokens for current user.

    - **active** (boolean): Active token or revoked
    - **token_type** (): Token type
    - **restricted** (): Restricted token or default
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to list user tokens.",
        )

    tokens = current_user.tokens
    if active is not None:
        tokens = list(filter(lambda token: token.active == active, tokens))
    if token_type is not None:
        tokens = list(filter(lambda token: token.token_type == token_type, tokens))
    if restricted is not None:
        tokens = list(filter(lambda token: token.restricted == restricted, tokens))
    return {
        "user_id": current_user.id,
        "username": current_user.username,
        "token": tokens,
    }


@app.get("/user", tags=["users"], response_model=data.UserResponse)
async def get_user_handler(
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.UserResponse:
    """
    Get current user.
    """
    user_id = current_user.id
    user = actions.get_user(session=db_session, user_id=user_id)

    return user


@app.get("/user/find", tags=["users"], response_model=data.UserResponse)
async def find_user_handler(
    token_restricted: bool = Depends(is_token_restricted_or_installation),
    _: Optional[models.User] = Depends(get_current_user_or_installation),
    username: Optional[str] = Query(None),
    email: Optional[str] = Query(None),
    user_id: Optional[uuid.UUID] = Query(None),
    db_session=Depends(yield_db_session_from_env),
) -> data.UserResponse:
    """
    Search for users.

    - **username** (string, null): Username
    - **email** (string, null): User email
    - **user_id** (uuid, null): User ID
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to find users.",
        )

    try:
        user = actions.get_user(
            session=db_session, username=username, email=email, user_id=user_id
        )
    except actions.UserInvalidParameters:
        raise HTTPException(
            status_code=400,
            detail="You must specify one of the following query parameters: username,email",
        )
    except actions.UserNotFound:
        raise HTTPException(status_code=404, detail="No users matched your query")
    return data.UserResponse(
        id=user.id,
        username=user.username,
        verified=user.verified,
        created_at=user.created_at,
        updated_at=user.updated_at,
        autogenerated=user.autogenerated,
    )


@app.get("/user/{user_id}", tags=["users"], response_model=data.UserResponse)
async def get_user_by_id_handler(
    user_id: uuid.UUID = Path(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.UserResponse:
    """
    Get user by ID.

    - **user_id** (uuid, null): User ID
    """
    if user_id != current_user.id:
        raise HTTPException(
            status_code=403, detail="You do not have permission to view this resource"
        )
    try:
        user = actions.get_user(session=db_session, user_id=user_id)
    except actions.UserInvalidParameters:
        raise HTTPException(status_code=400, detail="Invalid user id")
    except actions.UserNotFound:
        raise HTTPException(status_code=404, detail="No user with that user id")

    return user


@app.post("/confirm", tags=["users"], response_model=data.UserResponse)
async def verification_handler(
    token_restricted: bool = Depends(is_token_restricted),
    verification_code: str = Form(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.UserResponse:
    """
    Verify email of authenticated user.

    - **verification_code** (string): Verification code to complete user registration
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to verify accounts.",
        )

    try:
        user = actions.complete_verification(
            db_session, code=verification_code, user_id=current_user.id
        )
    except actions.VerificationEmailNotFound:
        raise HTTPException(
            status_code=404, detail="Could not find a verification email for this user"
        )
    except actions.VerificationIncorrectCode:
        raise HTTPException(status_code=400, detail="Invalid verification code")

    return user


# TODO(kompotkot): DEPRECATED @app.post("/reset")
@app.post("/reset", include_in_schema=False, response_model=data.ResetPasswordResponse)
@app.post(
    "/password/restore", tags=["users"], response_model=data.ResetPasswordResponse
)
async def restore_password_handler(
    background_tasks: BackgroundTasks,
    email: str = Form(...),
    db_session=Depends(yield_db_session_from_env),
) -> data.ResetPasswordResponse:
    """
    Generates link with reset password ID and send it to user email.

    - **email** (string): User email reset link will be send to
    """
    reset_status = None
    try:
        reset_object = actions.generate_reset_password(db_session, email=email)

        background_tasks.add_task(
            actions.send_reset_password_email, reset_object=reset_object, email=email,
        )
        reset_status = "ok"
    except actions.UserNotFound:
        raise HTTPException(status_code=404, detail="No user with provided email")
    except actions.ResetPasswordNotAllowed:
        raise HTTPException(
            status_code=403,
            detail="Insufficient time has passed since the last password reset",
        )

    return data.ResetPasswordResponse(reset_password=reset_status)


# TODO(kompotkot): Add service which marks all old reset passwords as "complete"
# which exist more then 24 hours.
# TODO(kompotkot): DEPRECATED @app.post("/reset_password")
@app.post("/password_reset", include_in_schema=False, response_model=data.UserResponse)
@app.post("/password/reset", tags=["users"], response_model=data.UserResponse)
async def reset_password_confirmation_handler(
    reset_id: uuid.UUID = Form(...),
    new_password: str = Form(...),
    db_session=Depends(yield_db_session_from_env),
) -> data.UserResponse:
    """
    Accepts user ID with new password, marks used reset ID as inactive.

    - **reset_id** (uuid): Reset password unique ID
    - **new_password** (string): New user password
    """
    try:
        user = actions.reset_password_confirmation(db_session, reset_id, new_password)
    except actions.UserNotFound:
        raise HTTPException(status_code=404, detail="No user for password reset")
    except actions.UserInvalidParameters:
        raise HTTPException(status_code=400, detail="Invalid user parameters")
    except actions.UserIncorrectPassword:
        raise HTTPException(status_code=401, detail="Incorrect password")
    except actions.PasswordInvalidParameters as invalid_password_error:
        raise HTTPException(
            status_code=422, detail=invalid_password_error.generic_error_message,
        )

    return user


# TODO(kompotkot): DEPRECATED @app.post("/profile/password")
@app.post(
    "/profile/password", include_in_schema=False, response_model=data.UserResponse
)
@app.post("/password/change", tags=["users"], response_model=data.UserResponse)
async def change_password_handler(
    token_restricted: bool = Depends(is_token_restricted),
    current_user: models.User = Depends(get_current_user),
    new_password: str = Form(...),
    current_password: str = Form(...),
    db_session=Depends(yield_db_session_from_env),
) -> data.UserResponse:
    """
    Change user password.

    - **new_password** (string): New user password
    - **current_password** (string): Current user password
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to change passwords.",
        )

    try:
        user = actions.change_password(
            db_session,
            new_password=new_password,
            current_password=current_password,
            user_id=current_user.id,
        )
    except actions.UserInvalidParameters:
        raise HTTPException(status_code=400, detail="Invalid user parameters")
    except actions.UserIncorrectPassword:
        raise HTTPException(status_code=401, detail="Incorrect password")
    except actions.PasswordInvalidParameters as invalid_password_error:
        raise HTTPException(
            status_code=422, detail=invalid_password_error.generic_error_message,
        )

    return user


@app.put("/user", tags=["users"], response_model=data.UserResponse)
async def update_user_handler(
    token_restricted: bool = Depends(is_token_restricted),
    first_name: Optional[str] = Form(None),
    last_name: Optional[str] = Form(None),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.UserResponse:
    """
    Update user information.

    - **first_name** (string): First user name
    - **last_name** (string):  Last user name
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to update users.",
        )

    try:
        user = actions.update_user(db_session, current_user.id, first_name, last_name)
    except actions.UserInvalidParameters:
        raise HTTPException(status_code=400, detail="Invalid user parameters")
    except Exception as err:
        logger.error(f"Unhandled error in update_user_handler: {str(err)}")
        raise HTTPException(status_code=500)

    return user


@app.delete("/user/{user_id}", tags=["users"], response_model=data.UserResponse)
async def delete_user_handler(
    request: Request,
    token_restricted: bool = Depends(is_token_restricted),
    user_id: uuid.UUID = Path(...),
    password: str = Form(None),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.UserResponse:
    """
    Delete user by ID. Regular user deletion impossible without provided password.

    - **user_id** (uuid): User ID
    - **password** (string): User password
    """
    # Autogenerated user deletion impossible without installation header.
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to delete users.",
        )
    if user_id != current_user.id:
        raise HTTPException(
            status_code=403, detail="You do not have permission to delete this resource"
        )
    autogenerated_user = autogenerated_user_token_check(request)
    if autogenerated_user != current_user.autogenerated:
        raise HTTPException(status_code=403, detail="Autogenerated user bypass failed")
    try:
        user = actions.delete_user(
            session=db_session, user_id=user_id, current_password=password,
        )
    except actions.UserInvalidParameters:
        raise HTTPException(status_code=400, detail="Invalid user parameters")
    except actions.UserNotFound:
        raise HTTPException(status_code=404, detail="No user with that user id")
    except actions.UserIncorrectPassword:
        raise HTTPException(status_code=401, detail="Incorrect password")

    return user


# TODO(kompotkot): DEPRECATED @app.get("/group/find")
@app.get("/group/find", include_in_schema=False, response_model=data.GroupFindResponse)
@app.get("/groups/find", tags=["groups"], response_model=data.GroupFindResponse)
async def find_group_handler(
    group_id: uuid.UUID = Query(...),
    _: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupFindResponse:
    """
    Search for groups.

    - **group_id** (uuid): Group ID
    """
    try:
        group_obj = actions.find_group(db_session, group_id=group_id)
    except actions.GroupNotFound:
        raise HTTPException(status_code=404, detail="No groups matched your query")

    return data.GroupFindResponse(
        id=group_obj.id, name=group_obj.name, autogenerated=group_obj.autogenerated
    )


# TODO(kompotkot): DEPRECATED @app.get("/group/{group_id}")
@app.get("/groups/{group_id}", tags=["groups"], response_model=data.GroupResponse)
@app.get(
    "/group/{group_id}", include_in_schema=False, response_model=data.GroupResponse
)
async def get_group_handler(
    request: Request,
    group_id: uuid.UUID = Path(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupResponse:
    """
    Get group detailed information if user has permissions to view this resource.

    - **group_id** (uuid): Group ID
    """
    try:
        group = actions.get_group(
            session=db_session, group_id=group_id, user_id=current_user.id
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )

    children_url: Optional[str] = None
    try:
        children_url = f"{request.url.path}/children"
    except Exception as e:
        pass

    return data.GroupResponse(
        id=group.id,
        name=group.name,
        autogenerated=group.autogenerated,
        subscriptions=[
            subscription_plan.subscription_plan_id
            for subscription_plan in group.subscriptions
        ],
        children_url=children_url,
        parent=group.parent,
        created_at=group.created_at,
        updated_at=group.updated_at,
    )


# TODO(kompotkot): DEPRECATED @app.get("/group/{group_id}/children")
@app.get(
    "/groups/{group_id}/children",
    tags=["groups"],
    response_model=data.GroupListResponse,
)
@app.get(
    "/group/{group_id}/children",
    include_in_schema=False,
    response_model=data.GroupListResponse,
)
async def get_group_children_handler(
    group_id: uuid.UUID = Path(...),
    current_user: models.User = Depends(get_current_user),
    limit: int = Query(10),
    offset: int = Query(0),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupListResponse:
    """
    Get list of children for group.

    - **group_id** (uuid): Group ID
    - **limit** (integer): Output result limit
    - **offset** (integer): Result output offset
    """
    try:
        actions.get_group(
            session=db_session, group_id=group_id, user_id=current_user.id
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )

    children = actions.get_group_children(
        db_session, group_id, limit=limit, offset=offset
    )

    children_responses = [
        data.GroupResponse(
            id=child.id,
            name=child.name,
            autogenerated=child.autogenerated,
            subscriptions=[],
            children_url=None,
            parent=child.parent,
            created_at=child.created_at,
            updated_at=child.updated_at,
        )
        for child in children
    ]
    return data.GroupListResponse(groups=children_responses)


@app.get("/groups", tags=["groups"], response_model=data.GroupUserListResponse)
async def get_groups_handler(
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupUserListResponse:
    """
    Get list of groups user belongs to.
    """
    groups_list: Optional[List[data.GroupUserResponse]] = []
    try:
        groups_list = actions.get_groups_for_user(db_session, user_id=current_user.id)
    except Exception as e:
        logger.error(f"Error getting list of groups for user: {str(e)}")
        raise HTTPException(status_code=500)

    groups_response = data.GroupUserListResponse(groups=groups_list)
    return groups_response


# TODO(kompotkot): DEPRECATED @app.post("/group")
@app.post("/groups", tags=["groups"], response_model=data.GroupResponse)
@app.post("/group", include_in_schema=False, response_model=data.GroupResponse)
async def create_group_handler(
    token_restricted: bool = Depends(is_token_restricted),
    group_name: str = Form(...),
    parent: uuid.UUID = Form(None),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupResponse:
    """
    Creates group as a group owner.

    - **group_name** (string): Group name
    - **parent** (uuid): Group parent if exists
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to create groups.",
        )

    try:
        group = actions.create_group(
            session=db_session,
            group_name=group_name,
            user=current_user,
            parent_id=parent,
        )
    except actions.GroupInvalidParameters:
        raise HTTPException(status_code=400, detail="Invalid group name or parent id")
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404, detail="There is no provided group with parent id"
        )
    except actions.GroupAlreadyExists:
        raise HTTPException(
            status_code=409,
            detail="There was a conflict when adding a group to the database",
        )
    except exceptions.UserGroupLimitExceeded as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500)

    return data.GroupResponse(
        id=group.id,
        name=group.name,
        autogenerated=group.autogenerated,
        subscriptions=[
            subscription_plan.subscription_plan_id
            for subscription_plan in group.subscriptions
        ],
        parent=group.parent,
        created_at=group.created_at,
        updated_at=group.updated_at,
    )


# TODO(kompotkot): DEPRECATED @app.post("/group/{group_id}/role")
@app.post(
    "/groups/{group_id}/role", tags=["groups"], response_model=data.GroupUserResponse
)
@app.post(
    "/group/{group_id}/role",
    include_in_schema=False,
    response_model=data.GroupUserResponse,
)
async def set_user_in_group_handler(
    token_restricted: bool = Depends(is_token_restricted),
    group_id: uuid.UUID = Path(...),
    user_type: models.Role = Form(...),
    username: Optional[str] = Form(None),
    email: Optional[str] = Form(None),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupUserResponse:
    """
    Add provided user to group.

    - **group_id** (uuid): Group ID
    - **user_type** (string): Required user permission in group
    - **username** (string): User name
    - **email** (string): User email
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to change group membership.",
        )

    try:
        # Check user permissions
        group_user = actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )
    try:
        group_user_response = actions.set_user_in_group(
            session=db_session,
            group_id=group_id,
            current_user_type=group_user.user_type,
            current_user_autogenerated=current_user.autogenerated,
            user_type=user_type.value,
            username=username,
            email=email,
        )
    except actions.LackOfUserSpace:
        raise HTTPException(
            status_code=403, detail="Space for users has been exhausted"
        )
    except actions.GroupInvalidParameters:
        raise HTTPException(status_code=400, detail="Invalid username/email")
    except actions.GroupNotFound:
        raise HTTPException(status_code=404, detail="No group with that id")
    except actions.UserNotFound:
        raise HTTPException(status_code=404, detail="No user with that username/email")
    except actions.NoPermissions:
        raise HTTPException(
            status_code=403, detail="You nave no permission to change roles in group"
        )
    except actions.NoInheritancePermission:
        raise HTTPException(
            status_code=403,
            detail="Please add user to parent group before he will be added to child",
        )
    return group_user_response


# TODO(kompotkot): DEPRECATED @app.get("/group/{group_id}/users")
@app.get(
    "/groups/{group_id}/users", tags=["groups"], response_model=data.UsersListResponse
)
@app.get(
    "/group/{group_id}/users",
    include_in_schema=False,
    response_model=data.UsersListResponse,
)
async def get_group_members_handler(
    token_restricted: bool = Depends(is_token_restricted),
    group_id: uuid.UUID = Path(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.UsersListResponse:
    """
    Get list of all group members.

    - **group_id** (uuid): Group ID
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to list group users.",
        )

    try:
        # Check user permissions
        group_user = actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
        group_users_response = actions.get_group_users(
            db_session, group_user.group_id, group_user.group_name
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )
    return group_users_response


@app.get(
    "/groups/{group_id}/subscriptions",
    tags=["groups"],
    response_model=data.SubscriptionUnitsListResponse,
)
async def get_group_subscriptions_handler(
    token_restricted: bool = Depends(is_token_restricted),
    group_id: uuid.UUID = Path(...),
    active: bool = Query(True),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.SubscriptionUnitsListResponse:
    """
    Get list of all active group subscriptions with units.

    - **group_id** (uuid): Group ID
    - **active** (boolean, null): Active subscription or not
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to list group users.",
        )

    try:
        # Check user permissions
        group_user = actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
        group_subscriptions = subscriptions.get_group_subscriptions(
            db_session, group_user.group_id, active=active
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )
    return data.SubscriptionUnitsListResponse(
        subscriptions=[
            data.SubscriptionUnitsResponse(
                group_id=subscription.group_id,
                plan_id=subscription.subscription_plan_id,
                plan_name=subscription.plan.name,
                plan_type=subscription.plan.plan_type,
                units=subscription.units,
            )
            for subscription in group_subscriptions
        ]
    )


# TODO(kompotkot): DEPRECATED @app.put("/group/{group_id}/name")
@app.post("/groups/{group_id}/name", tags=["groups"], response_model=data.GroupResponse)
@app.put(
    "/group/{group_id}/name", include_in_schema=False, response_model=data.GroupResponse
)
async def set_group_name_handler(
    token_restricted: bool = Depends(is_token_restricted),
    group_id: uuid.UUID = Path(...),
    group_name: str = Form(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupResponse:
    """
    Modify group name.

    - **group_id** (uuid): Group ID
    - **group_name** (string): Group name
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to change group names.",
        )

    try:
        # Check user permissions
        group_user = actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )
    if (
        group_user.user_type != models.Role.owner
        and group_user.user_type != models.Role.admin
    ):
        raise HTTPException(
            status_code=403, detail="You do not have permission to change group name"
        )

    try:
        group = actions.change_group_name(
            session=db_session, group_id=group_id, group_name=group_name
        )
    except actions.GroupNotFound:
        raise HTTPException(status_code=404, detail="No group with that id")

    return group


# TODO(kompotkot): DEPRECATED @app.delete("/group/{group_id}/role")
@app.delete(
    "/groups/{group_id}/role", tags=["groups"], response_model=data.GroupUserResponse
)
@app.delete(
    "/group/{group_id}/role",
    include_in_schema=False,
    response_model=data.GroupUserResponse,
)
async def delete_user_from_group_handler(
    token_restricted: bool = Depends(is_token_restricted),
    group_id: uuid.UUID = Path(...),
    username: Optional[str] = Form(None),
    email: Optional[str] = Form(None),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupUserResponse:
    """
    Removes user from group.

    - **group_id** (uuid): Group ID
    - **username** (string): User username
    - **email** (string): User email
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to change group membership.",
        )

    try:
        # Check user permissions
        group_user = actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )

    if (
        group_user.user_type != models.Role.owner
        and group_user.user_type != models.Role.admin
        and username != current_user.username
    ):
        raise HTTPException(
            status_code=403,
            detail="You do not have permission to delete user from group",
        )

    try:
        role_object = actions.delete_user_from_group(
            session=db_session,
            group_id=group_id,
            current_user_autogenerated=current_user.autogenerated,
            username=username,
            email=email,
        )
    except actions.LackOfUserSpace:
        raise HTTPException(
            status_code=403, detail="Space for users has been exhausted"
        )
    except actions.GroupInvalidParameters:
        raise HTTPException(status_code=400, detail="Invalid username/email")
    except actions.GroupNotFound:
        raise HTTPException(status_code=404, detail="No group with that id")
    except actions.UserNotFound:
        raise HTTPException(status_code=404, detail="No user with that username/email")
    except actions.RoleNotFound:
        raise HTTPException(status_code=404, detail="User does not belong group")
    except actions.NoPermissions:
        raise HTTPException(
            status_code=403, detail="You nave no permission to change roles in group"
        )

    return role_object


# TODO(kompotkot): DEPRECATED @app.delete("/group/{group_id}")
@app.delete("/groups/{group_id}", tags=["groups"], response_model=data.GroupResponse)
@app.delete(
    "/group/{group_id}", include_in_schema=False, response_model=data.GroupResponse
)
async def delete_group_handler(
    token_restricted: bool = Depends(is_token_restricted),
    group_id: uuid.UUID = Path(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupResponse:
    """
    Delete group by ID.

    - **group_id** (uuid): Group ID
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to delete groups.",
        )

    try:
        # Check user permissions
        group_user = actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )

    if (
        group_user.user_type != models.Role.owner
        and group_user.user_type != models.Role.admin
    ):
        raise HTTPException(
            status_code=403, detail="You do not have permission to delete this resource"
        )

    try:
        group = actions.delete_group(
            session=db_session, group_id=group_id, current_user=current_user
        )
    except actions.GroupNotFound:
        raise HTTPException(status_code=404, detail="No group with that id")
    except actions.NoPermissions:
        raise HTTPException(
            status_code=403, detail="You do not have permission to delete this resource"
        )

    return data.GroupResponse(
        id=group.id,
        name=group.name,
        autogenerated=group.autogenerated,
        subscriptions=[
            subscription_plan.subscription_plan_id
            for subscription_plan in group.subscriptions
        ],
        parent=group.parent,
        created_at=group.created_at,
        updated_at=group.updated_at,
    )


@app.get(
    "/groups/{group_id}/invites",
    tags=["groups"],
    response_model=data.GroupInvitesListResponse,
)
async def get_invites_handler(
    group_id: uuid.UUID = Path(...),
    active: bool = Query(None),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupInvitesListResponse:
    """
    Return list of group invites.

    - **group_id** (uuid): Group ID
    - **active** (boolean, null): Active invite or not
    """
    try:
        # Check user permissions
        actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )
    invites = actions.list_invites(db_session, group_id, active)

    return data.GroupInvitesListResponse(
        invites=[
            data.GroupInviteResponse(
                id=invite.id,
                group_id=invite.group_id,
                initiator_user_id=invite.initiator_user_id,
                email=invite.invited_email,
                active=invite.active,
                created_at=invite.created_at,
                updated_at=invite.updated_at,
            )
            for invite in invites
        ]
    )


@app.post(
    "/groups/{group_id}/invites/send",
    tags=["groups"],
    response_model=data.GroupInviteMessageResponse,
)
async def invite_send_handler(
    background_tasks: BackgroundTasks,
    group_id: uuid.UUID = Path(...),
    email: str = Form(None),
    user_type: models.Role = Form(models.Role.member),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupInviteMessageResponse:
    """
    Generate invite request.
    If user exists in database, then add user to group.
    If doesn't, then send invitation link to user email.

    - **group_id** (uuid): Group ID
    - **email** (string, null): User email
    - **user_type** (string): User permission in group
    """
    try:
        # Check user permissions
        actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )
    try:
        group = actions.get_group(db_session, group_id=group_id)
        free_space = actions.get_user_limit(db_session, group, 1)
        if not free_space:
            raise HTTPException(
                status_code=403, detail="Space for users has been exhausted"
            )
    except actions.GroupNotFound:
        raise HTTPException(status_code=404, detail="No group with that id")

    invite_response = data.GroupInviteMessageResponse()
    if email is not None:
        try:
            actions.get_user(session=db_session, email=email)
            invite_response.personal = True
            invite_response.message = (
                "User already exists, please add him to group manually"
            )
            return invite_response
        except actions.UserNotFound:
            invite_response.personal = True
        except actions.UserInvalidParameters:
            raise HTTPException(status_code=400, detail="Invalid user email")
    else:
        invite_response.personal = False

    try:
        invite = actions.create_invite(
            db_session, group_id, current_user.id, email, user_type
        )
        if invite_response.personal:
            background_tasks.add_task(
                actions.send_group_invite,
                invite_id=invite.id,
                email=email,
                initiator_email=current_user.email,
            )
        group_invite_link = group_invite_link_from_env(str(invite.id), email)
        invite_response.message = group_invite_link
    except actions.InviteNotAllowed:
        raise HTTPException(
            status_code=403,
            detail="Insufficient time has passed since the last invite",
        )

    return invite_response


@app.post("/invites/accept", tags=["groups"], response_model=data.GroupUserResponse)
async def invite_accept_handler(
    invite_id: uuid.UUID = Form(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupUserResponse:
    """
    Accept invite request.
    If the user is verified, adds user to desired group and marks the invitation as accepted.

    - **invite_id** (uuid): Invite ID to group
    """
    try:
        invite = actions.get_invite(db_session, invite_id)
        if not invite.active:
            raise HTTPException(status_code=400, detail="Invite is not active")
    except actions.InviteNotFound:
        raise HTTPException(
            status_code=404, detail="Invitation with provided id not found"
        )

    try:
        group = actions.get_group(db_session, group_id=invite.group_id)
        free_space = actions.get_user_limit(db_session, group, 1)
        if not free_space:
            raise HTTPException(
                status_code=403, detail="Space for users has been exhausted"
            )
    except actions.GroupNotFound:
        raise HTTPException(status_code=404, detail="No group with that id")

    if invite.invited_email is not None:
        if current_user.email != invite.invited_email:
            raise HTTPException(
                status_code=400, detail="You are not allowed to use this invite link"
            )
        else:
            user = actions.get_user(session=db_session, email=invite.invited_email)

        if not user.verified:
            raise HTTPException(status_code=400, detail="User is not verified")

        actions.update_invite(db_session, invite.id, active=False)

    try:
        group_user_response = actions.set_user_in_group(
            session=db_session,
            group_id=invite.group_id,
            current_user_type=data.Role.owner,
            current_user_autogenerated=False,
            user_type=invite.user_type,
            username=current_user.username,
            email=current_user.email,
        )
    except actions.UserInvalidParameters:
        raise HTTPException(status_code=400, detail="Invalid user email")
    except actions.UserNotFound:
        raise HTTPException(status_code=404, detail="No user with that email")
    except actions.NoPermissions:
        raise HTTPException(
            status_code=403, detail="You nave no permission to change roles in group",
        )

    return group_user_response


@app.delete(
    "/groups/{group_id}/invites",
    tags=["groups"],
    response_model=data.GroupInviteResponse,
)
async def invite_revoke_handler(
    group_id: uuid.UUID = Path(...),
    invite_id: uuid.UUID = Form(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.GroupInviteResponse:
    """
    Revoke group invite.

    - **group_id** (uuid): Group ID
    - **invite_id** (uuid): Invite ID
    """
    try:
        # Check user permissions
        group_user = actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )
    if (
        group_user.user_type != models.Role.owner
        and group_user.user_type != models.Role.admin
    ):
        raise HTTPException(
            status_code=403, detail="You do not have permission to change group name"
        )

    try:
        invite = actions.get_invite(db_session, invite_id)
        if not invite.active:
            raise HTTPException(status_code=400, detail="Invite is not active")
    except actions.InviteNotFound:
        raise HTTPException(
            status_code=404, detail="Invitation with provided id not found"
        )

    actions.update_invite(db_session, invite.id, active=False)

    return data.GroupInviteResponse(
        id=invite.id,
        group_id=invite.group_id,
        initiator_user_id=invite.initiator_user_id,
        email=invite.invited_email,
        active=invite.active,
        created_at=invite.created_at,
        updated_at=invite.updated_at,
    )


# TODO(kompotkot): DEPRECATED @app.get("/subscription/plans")
@app.get(
    "/subscriptions/plans",
    tags=["subscriptions"],
    response_model=data.SubscriptionPlanListResponse,
)
@app.get(
    "/subscription/plans",
    include_in_schema=False,
    response_model=data.SubscriptionPlanListResponse,
)
async def get_subscriptions_handler(
    db_session=Depends(yield_db_session_from_env),
) -> data.SubscriptionPlanListResponse:
    """
    Extract all subscription plans.
    """
    try:
        all_subscriptions = subscriptions.get_subscription_plans(
            db_session, public_or_not=True
        )
    except subscriptions.SubscriptionPlanNotFound:
        raise HTTPException(status_code=404, detail="There are no subscription plans")

    return data.SubscriptionPlanListResponse(
        plans=[subscription for subscription in all_subscriptions]
    )


# TODO(kompotkot): DEPRECATED @app.get("/subscription/stripe/webhook")
@app.post("/subscriptions/stripe/webhook", include_in_schema=False)
@app.post("/subscription/stripe/webhook", include_in_schema=False)
async def stripe_webhook_handler(
    request: Request, response: Response, db_session=Depends(yield_db_session_from_env),
) -> None:
    """
    Notify when an event happens in Stripe account.

    Docs: https://stripe.com/docs/webhooks/build
    """
    try:
        sig_header = request.headers.get("stripe-signature")
        raw_body = await request.body()
        event = stripe.Webhook.construct_event(
            payload=raw_body, sig_header=sig_header, secret=STRIPE_SIGNING_SECRET
        )
    except stripe.error.SignatureVerificationError as e:
        raise HTTPException(status_code=400, detail="Stripe event signature fail")
    except ValueError as e:
        logger.error(repr(e))
        raise HTTPException(status_code=400, detail="Stripe event process error")

    try:
        if (
            event.type == "customer.subscription.created"
            or event.type == "customer.subscription.updated"
        ):
            stripe_product_id = event.data.object["items"]["data"][0]["plan"]["product"]
            stripe_price_id = event.data.object["items"]["data"][0]["price"]["id"]
            plan = subscriptions.get_subscription_plan_by_stripe_data(
                db_session, stripe_product_id, stripe_price_id
            )
            if plan.plan_type == data.SubscriptionPlanType.seats.value:
                free_plan_id = actions.get_kv_variable(
                    db_session, "BUGOUT_GROUP_FREE_SUBSCRIPTION_PLAN"
                ).kv_value
            elif plan.plan_type == data.SubscriptionPlanType.events.value:
                free_plan_id = actions.get_kv_variable(
                    db_session, "BUGOUT_HUMBUG_MONTHLY_EVENTS_FREE_SUBSCRIPTION_PLAN"
                ).kv_value
            else:
                raise HTTPException(status_code=500)

            subscription_status = event.data.object.status
            stripe_subscription_id = event.data.object.id
            stripe_customer_id = event.data.object.customer
            stripe_units = event.data.object["items"]["data"][0].quantity

            group_subscription = subscriptions.get_group_subscription(
                session=db_session, stripe_customer_id=stripe_customer_id
            )
            if group_subscription is None:
                raise HTTPException(
                    status_code=404, detail="Subscription for group not found."
                )

            if subscription_status == "active":
                subscriptions.update_group_subscription_by_customer(
                    session=db_session,
                    group_id=group_subscription.group_id,
                    plan_id=group_subscription.subscription_plan_id,
                    stripe_customer_id=stripe_customer_id,
                    stripe_subscription_id=stripe_subscription_id,
                    units=stripe_units,
                    active=True,
                )
                subscriptions.update_group_subscription(
                    session=db_session,
                    group_id=group_subscription.group_id,
                    plan_id=free_plan_id,
                    active=False,
                )
                logger.info(
                    f"Subscription was activated for customer with id: {stripe_customer_id}"
                )
            elif subscription_status == "past_due":
                subscriptions.update_group_subscription_by_customer(
                    session=db_session,
                    group_id=group_subscription.group_id,
                    plan_id=group_subscription.subscription_plan_id,
                    stripe_customer_id=stripe_customer_id,
                    stripe_subscription_id=stripe_subscription_id,
                    units=stripe_units,
                    active=False,
                )
                subscriptions.update_group_subscription(
                    session=db_session,
                    group_id=group_subscription.group_id,
                    plan_id=free_plan_id,
                    active=True,
                )
                logger.info(
                    f"Subscription renew payment failed for customer with id: {event.data.object.customer}"
                )
            else:
                logger.info(f"Unhandled subscription status: {subscription_status}")
    except subscriptions.SubscriptionPlanNotFound:
        logger.error("Subscription plan not found with stripe_customer_id")

    response.status_code = status.HTTP_200_OK
    return


# TODO(kompotkot): DEPRECATED @app.get("/subscription/manage")
@app.post(
    "/subscriptions/manage",
    include_in_schema=False,
    response_model=data.SubscriptionManageResponse,
)
@app.post("/subscription/manage", include_in_schema=False)
async def add_subscription_handler(
    token_restricted: bool = Depends(is_token_restricted),
    group_id: uuid.UUID = Form(...),
    units_required: int = Form(...),
    plan_type: data.SubscriptionPlanType = Form(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.SubscriptionManageResponse:
    """
    Handle subscription requests from Frontend.
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to manage subscriptions.",
        )

    try:
        group = actions.get_group(db_session, group_id=group_id)
        if group.parent is not None:
            raise HTTPException(
                status_code=403,
                detail="You cannot add subscriptions to children - please modify your subscription to the parent group.",
            )
        group_user = actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
        if group_user.user_type != models.Role.owner:
            raise HTTPException(
                status_code=403,
                detail="You do not have permission to manage group subscription",
            )

        plan = subscriptions.get_subscription_plan_by_units(
            db_session, plan_type, units_required
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )
    except subscriptions.SubscriptionPlanNotFound:
        raise HTTPException(status_code=404, detail="No subscription plan with that id")

    if plan.default_units is not None:
        units_required = plan.default_units
    try:
        if plan.stripe_price_id is not None and plan.stripe_product_id is not None:
            subscription_response = subscriptions.add_volume_subscription(
                db_session, plan, group, units_required
            )
        else:
            subscription_response = subscriptions.add_internal_subscription(
                db_session, plan, group, units_required
            )
    except subscriptions.GroupSubscriptionAlreadyExists:
        raise HTTPException(
            status_code=409, detail="Group subscription already attached to group",
        )
    except Exception as e:
        logger.error(repr(e))
        raise HTTPException(status_code=500, detail="Stripe customer/session error")

    return subscription_response


@app.delete(
    "/subscription/manage",
    include_in_schema=False,
    response_model=data.SubscriptionPlanListResponse,
)
async def delete_subscription_handler(
    token_restricted: bool = Depends(is_token_restricted),
    group_id: uuid.UUID = Form(...),
    plan_type: data.SubscriptionPlanType = Form(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.SubscriptionPlanListResponse:
    """
    Deletes all group subscriptions with proper subscription plan type.
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to manage subscriptions.",
        )
    try:
        group = actions.get_group(db_session, group_id=group_id)
        if group.parent is not None:
            raise HTTPException(
                status_code=403,
                detail="You cannot add subscriptions to children - please modify your subscription to the parent group.",
            )
        group_user = actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
        if group_user.user_type != models.Role.owner:
            raise HTTPException(
                status_code=403,
                detail="You do not have permission to manage group subscription",
            )

        plans = subscriptions.get_subscription_plans(db_session, plan_type=plan_type)
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )
    except subscriptions.SubscriptionInvalidParameters:
        raise HTTPException(
            status_code=400, detail="Wrong plan id and group id provided"
        )

    plans_obj_response = []
    for plan in plans:
        group_subscription = subscriptions.get_group_subscription(
            db_session, group_id=group.id, plan_id=plan.id
        )
        if group_subscription is not None:
            subscriptions.remove_group_subscription(
                session=db_session, group_id=group.id, plan_id=plan.id
            )
            plans_obj_response.append(
                data.SubscriptionPlanResponse(
                    id=plan.id, name=plan.name, description=plan.description,
                )
            )

    return data.SubscriptionPlanListResponse(plans=plans_obj_response)


@app.post(
    "/applications", tags=["applications"], response_model=data.ApplicationResponse
)
async def create_application_handler(
    token_restricted: bool = Depends(is_token_restricted),
    group_id: uuid.UUID = Form(...),
    name: str = Form(...),
    description: str = Form(None),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.ApplicationResponse:
    """
    Creates new application.

    - **group_id** (uuid): Group ID application connect to
    - **name** (string): Application name
    - **description** (string): Application short description
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to create groups.",
        )

    try:
        # Check user permissions
        actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=group_id
        )
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )
    except Exception as e:
        raise HTTPException(status_code=500)

    try:
        application = actions.create_application(
            db_session, group_id, name, description
        )
    except Exception as e:
        raise HTTPException(status_code=500)

    return data.ApplicationResponse(
        id=application.id,
        group_id=application.group_id,
        name=application.name,
        description=application.description,
    )


@app.get(
    "/applications/{application_id}",
    tags=["applications"],
    response_model=data.ApplicationResponse,
)
async def get_application_handler(
    token_restricted: bool = Depends(is_token_restricted),
    application_id: uuid.UUID = Path(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.ApplicationResponse:
    """
    Return application.

    - **application_id** (uuid): Application ID
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to create groups.",
        )
    try:
        application = actions.get_applications(
            db_session, application_id=application_id
        )[0]

        # Check user permissions
        actions.check_user_type_in_group(
            db_session, user_id=current_user.id, group_id=application.group_id
        )
    except exceptions.ApplicationsNotFound:
        raise HTTPException(status_code=404, detail="No application with that id")
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404, detail="You do not have permission to view this resource",
        )
    except Exception as e:
        raise HTTPException(status_code=500)

    return data.ApplicationResponse(
        id=application.id,
        group_id=application.group_id,
        name=application.name,
        description=application.description,
    )


@app.get(
    "/applications",
    tags=["applications"],
    response_model=data.ApplicationsListResponse,
)
async def list_applications_handler(
    token_restricted: bool = Depends(is_token_restricted),
    group_id: uuid.UUID = Query(None),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.ApplicationsListResponse:
    """
    Return list of applications for group.

    - **group_id** (uuid, null): Group ID
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to create groups.",
        )
    try:
        if group_id is not None:
            # Check user permissions
            actions.check_user_type_in_group(
                db_session, user_id=current_user.id, group_id=group_id
            )
            groups_ids = [group_id]
        else:
            groups_list = actions.get_groups_for_user(
                db_session, user_id=current_user.id
            )
            groups_ids = [group.group_id for group in groups_list]
    except actions.GroupNotFound:
        raise HTTPException(
            status_code=404,
            detail="No group with that group id or you do not have permission to view this resource",
        )
    except Exception as e:
        raise HTTPException(status_code=500)

    try:
        applications = actions.get_applications(db_session, groups_ids=groups_ids)
    except exceptions.ApplicationsNotFound:
        raise HTTPException(status_code=404, detail="There are no applications")
    except Exception as e:
        raise HTTPException(status_code=500)

    return data.ApplicationsListResponse(
        applications=[
            data.ApplicationResponse(
                id=application.id,
                group_id=application.group_id,
                name=application.name,
                description=application.description,
            )
            for application in applications
        ]
    )


@app.delete(
    "/applications/{application_id}",
    tags=["applications"],
    response_model=data.ApplicationResponse,
)
async def delete_application_handler(
    token_restricted: bool = Depends(is_token_restricted),
    application_id: uuid.UUID = Path(...),
    current_user: models.User = Depends(get_current_user),
    db_session=Depends(yield_db_session_from_env),
) -> data.ApplicationResponse:
    """
    Delete application.

    - **application_id** (uuid): Application ID
    """
    if token_restricted:
        raise HTTPException(
            status_code=403,
            detail="Restricted tokens are not authorized to create groups.",
        )

    try:
        groups_list = actions.get_groups_for_user(db_session, user_id=current_user.id)
        groups_ids = [group.group_id for group in groups_list]
        application = actions.delete_application(db_session, application_id, groups_ids)
    except exceptions.ApplicationsNotFound:
        raise HTTPException(status_code=404, detail="No application with that id")
    except Exception as e:
        logger.error(e)
        raise HTTPException(status_code=500)

    return data.ApplicationResponse(
        id=application.id,
        group_id=application.group_id,
        name=application.name,
        description=application.description,
    )
