import binascii
import json
import os
import secrets
from hashlib import sha1

from fastapi import Request
from passlib.context import CryptContext
from starlette.exceptions import HTTPException
from starlette.status import HTTP_102_PROCESSING, HTTP_404_NOT_FOUND, HTTP_425_TOO_EARLY

from core.config import settings
from core.db import redis_session_client
from models import User

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


async def create_sesssion_token(user: User, remember_me: bool, request: Request) -> str:
    session_token = secrets.token_hex(nbytes=16)
    expire_time = (
        settings.SESSION_EXPIRE_TIME_EXTENDED
        if remember_me
        else settings.SESSION_EXPIRE_TIME
    )

    active_sessions = await redis_session_client.client.get(
        f"user_{user.id}_sessions", encoding="utf-8"
    )

    if active_sessions:
        active_sessions = json.loads(active_sessions)
    else:
        active_sessions = {"uid": user.id, "sessions": []}

    active_sessions["sessions"].append(
        {
            "token": session_token,
            "ua": request.headers.get("user-agent"),
            "ip": request.client.host,
        }
    )

    data = {
        session_token: user.id,
        f"user_{user.id}_sessions": json.dumps(active_sessions),
    }

    await redis_session_client.client.mset(data)
    await redis_session_client.client.expire(session_token, expire_time)
    return session_token


async def create_2fa_temp_token(user: User, remember_me: bool) -> str:
    session_token = secrets.token_hex(nbytes=16)

    await redis_session_client.client.setex(
        f"two_fa_temp_{session_token}",
        settings.TWO_FA_TIMEOUT * 1000,
        json.dumps({"user": user.id, "remember_me": remember_me}),
    )

    return session_token


async def create_passwordless_create_token() -> str:
    token = secrets.token_hex(nbytes=16)

    await redis_session_client.client.setex(
        f"password_less_{token}",
        settings.PASSWORD_LESS_CREATE_TIMEOUT * 1000,
        "-1",
    )

    return token


async def authorize_passwordless_token(user: User, token: str) -> bool:
    value = await redis_session_client.client.get(
        f"password_less_{token}",
    )

    if value == None:
        raise HTTPException(status_code=HTTP_404_NOT_FOUND,
                            detail="Invalid token!")
    elif int(value) == -1:
        await redis_session_client.client.setex(
            f"password_less_{token}",
            settings.PASSWORD_LESS_CREATE_TIMEOUT * 1000,
            user.id,
        )
        return True


async def verify_passwordless_token(token: str) -> int:
    value = (await redis_session_client.client.get(
        f"password_less_{token}",
    )).decode("UTF-8")

    if value == None:
        raise HTTPException(
            status_code=HTTP_404_NOT_FOUND,
            detail="Invalid token!"
        )

    elif value == "-1":
        raise HTTPException(
            status_code=HTTP_425_TOO_EARLY,
            detail="Waiting for authorization!"
        )
    else:
        await redis_session_client.client.delete(
            f"password_less_{token}",
        )
        return int(value)


async def create_2fa_enable_temp_token(user: User, totp_secret: str):
    await redis_session_client.client.setex(
        f"two_fa_enable_temp_{user.id}",
        settings.TWO_FA_TIMEOUT * 1000,
        totp_secret
    )
    return


def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password: str) -> str:
    return pwd_context.hash(password)


def get_uid_hash(uid: str) -> str:
    hasher = sha1()
    hasher.update(bytes(f"uid_{uid}", "utf-8"))
    return hasher.hexdigest()[3:10]