# coding: utf-8
import asyncio
from datetime import datetime
from typing import Dict, List  # noqa: F401

from fastapi import (  # noqa: F401
    APIRouter,
    Body,
    Cookie,
    Depends,
    Form,
    Header,
    Path,
    Query,
    Response,
    Security,
    status,
    HTTPException,
)

from server.data.definitions import Groups, ErrorTypes, API_SUB_PATH, Messages
from server.models.extra_models import TokenModel  # noqa: F401
from server.models.error import Error
from server.models.token import Token
from server.security_api import get_token_ApiKeyAuth, checkTokenCapabilities
from . import sqliteClient
from .authentication_api import hasSqlInjectionCharacters, createNewToken
from ..utils.password import LOGIN_FAILURE_DELAY

router = APIRouter(prefix=API_SUB_PATH)

# Definitions
ADMIN = "admin"
DEVELOPER = "developer"
TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"


def checkTimeFormat(timeStamp: str) -> bool:
    """
    Checks if the time format does correspond to the definition %Y-%m-%d %H:%M:%S

    :param timeStamp: The time stamp.
    :return: True if the time stamp format is correct. Else False.
    """
    try:
        datetime.strptime(timeStamp, TIMESTAMP_FORMAT)
        return True
    except ValueError:
        return False


def validateTimeStamps(validateStart: str, validateEnd: str) -> bool:
    """
    Checks if the start and end time stamps are valid.

    :param validateStart: The start time stamp.
    :param validateEnd: The end time stamp.
    :return: True if the start and end time stamps are valid. Else False.
    """
    start = datetime.strptime(validateStart, TIMESTAMP_FORMAT)
    end = datetime.strptime(validateEnd, TIMESTAMP_FORMAT)
    return end > start


@router.get(
    "/tokens",
    responses={
        200: {"model": List[Token], "description": "Gets all tokens related to the user."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
    },
    tags=["tokens"],
    response_model_by_alias=True,
)
async def tokens_get(token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth)) -> List[Token]:
    """Get all the available tokens. The tokens returned depends from the user bound to the X-API-KEY."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.TOKEN_ADMINISTRATION.value)

    # --------------------------------------------- Get and return tokens --------------------------------------------
    tokens = []
    tokensUser = sqliteClient.getTokensUser(token=token_ApiKeyAuth)

    # If the user is not 'admin' or 'developer' it will only return his own api keys from the user e.g. customer
    # In this way an admin or developer is able to manage other users api tokens
    user = tokensUser if tokensUser not in [ADMIN, DEVELOPER] else None

    dbTokens = sqliteClient.getUsersApiTokens(whereUser=user, getSessionToken=False)

    for token in dbTokens:
        tokens.append(
            Token(
                token="",
                tokenName=token.tokenName,
                user=token.userName,
                validityStart=token.validityStart,
                validityEnd=token.validityEnd,
            )
        )

    return tokens


"""
POST with Form
{
  "token": "",
  "tokenName": "chrigusToken",
  "user": "developer",
  "validityStart": "2024-12-27 15:51:00",
  "validityEnd": "2025-12-27 15:00:00"
}
"""


@router.post(
    "/tokens",
    responses={
        201: {"model": Token, "description": "Shows the created Token."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
    },
    tags=["tokens"],
    response_model_by_alias=True,
)
async def tokens_post(
    token: Token = Body(description="Requestbody for the POST action."),
    token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth),
) -> Token:
    """Create a new token."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.TOKEN_ADMINISTRATION.value)

    # ------------------------------------------------ Check body data -----------------------------------------------
    # Check if the body is received
    if token is None or None in [token.token, token.token_name, token.user, token.validity_start, token.validity_end]:
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.REQUEST_IS_EMPTY.value,
            message=Messages.REQUEST_IS_EMPTY.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # check if the field where not empty
    if "" in [token.token_name, token.user, token.validity_start, token.validity_end]:
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.TOKEN_INFO_MISSING.value,
            message=Messages.TOKEN_INFO_MISSING.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # Check is the token was not send
    if token.token != "":
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.TOKEN_NOT_NECESSARY.value,
            message=Messages.TOKEN_NOT_NECESSARY.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # Check if the fields does not exceed the max length of the database
    for field in [[token.token_name, 30], [token.user, 50], [token.validity_start, 30], [token.validity_end, 30]]:
        if len(field[0]) > field[1]:
            errorResponse = Error(
                status=status.HTTP_400_BAD_REQUEST,
                type=ErrorTypes.FIELD_LENGTH_EXCEEDED.value,
                message=Messages.FIELD_LENGTH_EXCEEDED.value.format(field[0], field[1]),
            )
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # Check if the timestamp has the valid format
    for timeStamp in [token.validity_start, token.validity_end]:
        if not checkTimeFormat(timeStamp):
            errorResponse = Error(
                status=status.HTTP_400_BAD_REQUEST,
                type=ErrorTypes.TIMESTAMP_FORMAT_ERROR.value,
                message=Messages.TIMESTAMP_FORMAT.value.format(timeStamp),
            )
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # Validate if the timestamps are reasonable: validityStart < validityEnd
    if not validateTimeStamps(token.validity_start, token.validity_end):
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.TIMESTAMP_VALIDATION_ERROR.value,
            message=Messages.TIMESTAMP_VALIDATION.value.format(token.validity_end, token.validity_start),
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # ------------------------------------------------ Injection test ------------------------------------------------

    for string in [token.token_name, token.user, token.validity_start, token.validity_end]:
        if hasSqlInjectionCharacters(string):
            errorResponse = Error(
                status=status.HTTP_400_BAD_REQUEST,
                type=ErrorTypes.INJECTION_DETECTED.value,
                message=Messages.INJECTION_DETECTED.value,
            )
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # ------------------------------------ Check if the given user or token exist ------------------------------------

    # Check if the user does exist
    if not sqliteClient.checkUserExists(token.user):
        await asyncio.sleep(LOGIN_FAILURE_DELAY)  # brute force protection, delay based

        errorResponse = Error(
            status=status.HTTP_404_NOT_FOUND,
            type=ErrorTypes.USER_NOT_FOUND.value,
            message=Messages.USER_NOT_FOUND.value,
        )
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=errorResponse.dict())

    # Check if the tokenName already exists
    if sqliteClient.checkTokenNameExist(token.token_name):
        await asyncio.sleep(LOGIN_FAILURE_DELAY)  # brute force protection, delay based

        errorResponse = Error(
            status=status.HTTP_404_NOT_FOUND,
            type=ErrorTypes.TOKEN_NAME_EXISTS_ERROR.value,
            message=Messages.TOKEN_NAME_EXISTS.value.format(token.token_name),
        )
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=errorResponse.dict())

    # ---------------------------------------- Create and return the new token ---------------------------------------

    newToken = await createNewToken(
        requestedUser=token.user,
        isSessionToken=False,
        tokenName=token.token_name,
        validityStart=token.validity_start,
        validityEnd=token.validity_end,
    )

    return Token(
        token=newToken,
        tokenName=token.token_name,
        user=token.user,
        validityStart=token.validity_start,
        validityEnd=token.validity_end,
    )


# POST with /tokens?tokenName=yourName
@router.delete(
    "/tokens",
    responses={
        200: {"model": Token, "description": "Deleted the token."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
        404: {"model": Error, "description": "File not found."},
    },
    tags=["tokens"],
    response_model_by_alias=True,
)
async def tokens_token_name_delete(
    tokenName: str = Query(None, description="The name of the token."),
    token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth),
) -> Token:
    """Delete a specific token."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.TOKEN_ADMINISTRATION.value)

    # ------------------------------------------------ Check tokenName -----------------------------------------------
    # Check if the tokenName is received
    if tokenName is None:
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.REQUEST_IS_EMPTY.value,
            message=Messages.REQUEST_IS_EMPTY.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # check if the field where not empty
    if tokenName == "":
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.REQUEST_IS_EMPTY.value,
            message=Messages.REQUEST_IS_EMPTY.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # Check if the fields does not exceed the max length of the database
    for field in [[tokenName, 30]]:
        if len(field[0]) > field[1]:
            errorResponse = Error(
                status=status.HTTP_400_BAD_REQUEST,
                type=ErrorTypes.FIELD_LENGTH_EXCEEDED.value,
                message=Messages.FIELD_LENGTH_EXCEEDED.value.format(field[0], field[1]),
            )
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # ------------------------------------------------ Injection test ------------------------------------------------

    if hasSqlInjectionCharacters(tokenName):
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.INJECTION_DETECTED.value,
            message=Messages.INJECTION_DETECTED.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # ---------------------------------------- Check if the given token exist ----------------------------------------

    # Check if the tokenName does not exist
    if not sqliteClient.checkTokenNameExist(tokenName):
        errorResponse = Error(
            status=status.HTTP_404_NOT_FOUND,
            type=ErrorTypes.TOKEN_NAME_NOT_EXISTS_ERROR.value,
            message=Messages.TOKEN_NAME_NOT_EXISTS.value.format(tokenName),
        )
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=errorResponse.dict())

    # -------------------------------------- Delete and return the deleted token -------------------------------------

    # Figure out to which user the token relates
    user = sqliteClient.getTokensUserByTokenName(tokenName)

    # Get the detailed user information
    userData = sqliteClient.getUsersPasswords(whereUser=user)[0]

    # Retrieve the existing token data for the return (ACK)
    tokenData = sqliteClient.getTokenData(tokenName=tokenName)

    # Delete the token
    await sqliteClient.deleteApiToken(userData=userData, isSessionToken=False, tokenName=tokenName)

    return Token(
        token="",
        tokenName=tokenData.tokenName,
        user=tokenData.userName,
        validityStart=tokenData.validityStart,
        validityEnd=tokenData.validityEnd,
    )
