# AUTOGENERATED! DO NOT EDIT! File to edit: notebooks/API_User.ipynb (unless otherwise specified).

__all__ = ["User"]

# Cell

from typing import *

# Internal Cell

import os

import pandas as pd
from fastcore.foundation import patch
import qrcode
from qrcode.image.pil import PilImage

from ..logger import get_logger, set_level
from ..helper import (
    get_data,
    post_data,
    delete_data,
    generate_df,
    get_attributes_from_instances,
    check_and_append_otp_query_param,
)

from .client import Client

# Internal Cell

logger = get_logger(__name__)

# Cell


class User:
    """A class for managing users in the server.

    !!! info

        The `create`, `enable`, `disable` and `ls` method access is restricted only to the super users.
    """

    USER_COLS = [
        "uuid",
        "username",
        "email",
        "super_user",
        "is_mfa_active",
        "disabled",
        "created",
        "subscription_type",
        "first_name",
        "last_name",
    ]

    def __init__(
        self,
        uuid: str,
        username: Optional[str] = None,
        first_name: Optional[str] = None,
        last_name: Optional[str] = None,
        email: Optional[str] = None,
        subscription_type: Optional[str] = None,
        super_user: Optional[bool] = None,
        disabled: Optional[str] = None,
        created: Optional[str] = None,
        is_mfa_active: Optional[bool] = None,
    ):
        """Constructs a new User instance.

        Args:
            uuid: User uuid.
            username: The username of the user.
            first_name: The first name of the user.
            last_name: The last name of the user.
            email: The email address of the user.
            subscription_type: User subscription type. Currently, the API supports only the following subscription
                types **small**, **medium** and **large**.
            super_user: Flag to indicate the user type.
            disabled: Flag to indicate the status of the user.
            created: User creation date.
            is_mfa_active: Flag to indicate the user's MFA status.
        """

        self.uuid = uuid
        self.username = username
        self.first_name = first_name
        self.last_name = last_name
        self.email = email
        self.subscription_type = subscription_type
        self.super_user = super_user
        self.disabled = disabled
        self.created = created
        self.is_mfa_active = is_mfa_active

    @staticmethod
    def create(
        *,
        username: str,
        first_name: str,
        last_name: str,
        email: str,
        password: str,
        subscription_type: str,
        super_user: bool = False,
        otp: Optional[str] = None,
    ) -> pd.DataFrame:
        """Create a new user in the server.

        Args:
            username: The username for the new user.
            first_name: The first name for the new user.
            last_name: The last name for the new user.
            email: The email for the new user.
            password: The password for the new user.
            subscription_type: User subscription type. Currently, the API supports only the following subscription
                types **small**, **medium** and **large**.
            super_user: If set to **True**, then the new user will have super user privilages.
                If **None**, then the default value **False** will be used to create a non-super user.
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if the MFA is enabled for your account.

        Returns:
                A pandas DataFrame encapsulating the details of the newly created user.

        Raises:
            ConnectionError: If the server address is invalid or not reachable.
            ValueException: If the OTP is invalid.
            ValueError: If the username or email is already present in the server.

        An example to create a new user:

        ```python
            new_user = User.create(
                username="example_username",
                first_name="example_first_name",
                last_name="example_last_name",
                email="example@example.com",
                password="example_password",
            )
        ```
        """
        req_data = dict(
            username=username,
            first_name=first_name,
            last_name=last_name,
            email=email,
            subscription_type=subscription_type,
            super_user=super_user,
            password=password,
            otp=otp,
        )

        response = Client._post_data(relative_url=f"/user/", data=req_data)

        return pd.DataFrame(response, index=[0])[User.USER_COLS]

    @staticmethod
    def ls(
        offset: int = 0,
        limit: int = 100,
        disabled: bool = False,
        otp: Optional[str] = None,
    ) -> List["User"]:
        """Return the list of User instances available in the server.

        Args:
            offset: The number of users to offset at the beginning. If **None**, then the default value **0** will be used.
            limit: The maximum number of users to return from the server. If None, then the default value 100 will be used.
            disabled: If set to **True**, then only the deleted users will be returned. Else, the default value **False** will
                be used to return only the list of active users.
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if the MFA is enabled for your account.

        Returns:
            A list of User instances available in the server.

        Raises:
            ValueException: If the OTP is invalid.
            ConnectionError: If the server address is invalid or not reachable.

        An example to get the user's list from the server:

        ```python
        User.ls()
        ```
        """
        url = f"/user/?disabled={disabled}&offset={offset}&limit={limit}"
        users = Client._get_data(
            relative_url=check_and_append_otp_query_param(url, otp)
        )

        ux = [
            User(
                uuid=user["uuid"],
                username=user["username"],
                first_name=user["first_name"],
                last_name=user["last_name"],
                email=user["email"],
                subscription_type=user["subscription_type"],
                super_user=user["super_user"],
                disabled=user["disabled"],
                created=user["created"],
                is_mfa_active=user["is_mfa_active"],
            )
            for user in users
        ]

        return ux

    @staticmethod
    def as_df(ux: List["User"]) -> pd.DataFrame:
        """Return the details of User instances as a pandas dataframe.

        Args:
            ux: List of user instances.

        Returns:
            Details of all the User in a dataframe.

        Raises:
            ConnectionError: If the server address is invalid or not reachable.

        An example to list the available users:

        ```python
        ux = Model.ls()
        Model.as_df(ux)
        ```
        """
        lists = get_attributes_from_instances(ux, User.USER_COLS)  # type: ignore
        return generate_df(lists, User.USER_COLS)

    @staticmethod
    def disable(user: Union[str, List[str]], otp: Optional[str] = None) -> pd.DataFrame:
        """Disable one or more users.

        Args:
            user: user_uuid/username to disabled. To disable multiple users, please pass the uuids/names as a list.
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if the MFA is enabled for your account.

        Returns:
            A pandas DataFrame encapsulating the details of the disabled user.

        Raises:
            ValueException: If the OTP is invalid.
            ConnectionError: If the server address is invalid or not reachable.

        An example to disable a user:

        ```python
        User.disable(user=username)
        ```
        """
        _users = user if isinstance(user, list) else [user]
        response_list = []
        for user in _users:
            user_uuid = User.details(user=user, otp=otp)["uuid"]
            url = f"/user/{user_uuid}"
            response = Client._delete_data(
                relative_url=check_and_append_otp_query_param(url, otp)
            )
            response_list.append(response)

        return pd.DataFrame(response_list)[User.USER_COLS]

    @staticmethod
    def enable(user: Union[str, List[str]], otp: Optional[str] = None) -> pd.DataFrame:
        """Enable one or more disabled users.

        Args:
            user: user_uuid/username to enable. To enable multiple users, please pass the uuids/names as a list.
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if the MFA is enabled for your account.

        Returns:
            A pandas DataFrame encapsulating the details of the enabled user.

        Raises:
            ValueException: If the OTP is invalid.
            ConnectionError: If the server address is invalid or not reachable.

        An example to enable a disabled user:

        ```python
        User.enable(user=username)
        ```
        """

        _users = user if isinstance(user, list) else [user]
        response_list = []
        for user in _users:
            user_uuid = User.details(user=user, otp=otp)["uuid"]
            url = f"/user/{user_uuid}/enable"
            response = Client._get_data(
                relative_url=check_and_append_otp_query_param(url, otp)
            )
            response_list.append(response)

        return pd.DataFrame(response_list)[User.USER_COLS]

    @staticmethod
    def details(
        user: Optional[str] = None, otp: Optional[str] = None
    ) -> Dict[str, Union[str, bool]]:
        """Get user details

        Please do not pass the optional user parameter unless you are a super user. Only a
        super user can view details for other users.

        Args:
            user: Account user_uuid/username to get details. If not passed, then the currently logged-in
                details will be returned.
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if the MFA is enabled for your account.

        Returns:
            A dict containing the details of the user

        Raises:
            ValueException: If the OTP is invalid.
            ConnectionError: If the server address is invalid or not reachable.

        An example to get details of the currently logged-in user:

        ```python
        User.details()
        ```
        """
        return Client._post_data(
            relative_url=f"/user/details", data=dict(user_uuid_or_name=user, otp=otp)
        )

    @staticmethod
    def update(
        user: Optional[str] = None,
        username: Optional[str] = None,
        first_name: Optional[str] = None,
        last_name: Optional[str] = None,
        email: Optional[str] = None,
        password: Optional[str] = None,
        otp: Optional[str] = None,
    ) -> pd.DataFrame:
        """Update existing user details in the server.

         Please do not pass the optional user parameter unless you are a super user. Only a
         super user can update details for other users.

        Args:
            user: Account user_uuid/username to update. If not passed, then the default
                value **None** will be used to update the currently logged-in user details.
            username: New username for the user.
            first_name: New first name for the user.
            last_name: New last name for the user.
            email: New email for the user.
            password: New password for the user.
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if the MFA is enabled for your account.

        Returns:
            A pandas DataFrame encapsulating the updated user details.

        Raises:
            ValueException: If the OTP is invalid.
            ConnectionError: If the server address is invalid or not reachable.

        An example to update the username for an existing user:

        ```python
        User.update(username="new_username")
        ```
        """
        user_uuid = User.details(user=user, otp=otp)["uuid"]
        body = dict(
            username=username,
            first_name=first_name,
            last_name=last_name,
            email=email,
            password=password,
            otp=otp,
        )

        response = Client._post_data(
            relative_url=f"/user/{user_uuid}/update", data=body
        )

        return pd.DataFrame(response, index=[0])[User.USER_COLS]

    @staticmethod
    def _get_mfa_provision_url(otp: Optional[str] = None) -> str:
        """Get MFA provisioning url

        Args:
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if the MFA is enabled for your account.

        Returns:
            The provisioning url
        """
        url = f"/user/mfa/generate"
        response = Client._get_data(
            relative_url=check_and_append_otp_query_param(url, otp)
        )
        return response["mfa_url"]

    @staticmethod
    def enable_mfa(otp: Optional[str] = None) -> PilImage:
        """Enable MFA for the user

        This method will generate a QR code which can be scanned by an authenticator app,
        such as Google Authenticator.

        Args:
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if the MFA is enabled for your account.

        Returns:
            The generated QR code

        An example to enable MFA:

        ```python
        User.enable_mfa()
        ```
        """
        qr_code = qrcode.make(User._get_mfa_provision_url(otp))

        return qr_code

    @staticmethod
    def activate_mfa(otp: int) -> pd.DataFrame:
        """Activate MFA for the user

        If not enabled, please call the `enable_mfa` method first before calling this method.

        Args:
            otp: Dynamically generated six-digit verification code from the authenticator app

        Returns:
            A pandas DataFrame encapsulating the MFA activated user details

        Raises:
            ValueError: If the user entered six-digit verification code is invalid

        An example to activate MFA:

        ```python
        User.activate_mfa(otp)
        ```
        """
        response = Client._post_data(
            relative_url="/user/mfa/activate",
            data=dict(user_otp=otp),
        )

        return pd.DataFrame(response, index=[0])

    @staticmethod
    def disable_mfa(
        user: Optional[str] = None, otp: Optional[str] = None
    ) -> pd.DataFrame:
        """Disable MFA for the user

        Please do not pass the user parameter unless you are a super user. Only
        a super user can disable MFA for other users.

        Args:
            user: Account user_uuid/username to disable MFA. If not passed, then the default
                value **None** will be used to disable MFA for the currently logged-in user.
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if the MFA is enabled for your account.

        Returns:
            A pandas DataFrame encapsulating the MFA disabled user details

        Raises:
            ValueError: If a non-super user tries to disable MFA for other users
            ValueException: If the OTP is invalid.
            ConnectionError: If the server address is invalid or not reachable.

        An example to disable MFA:

        ```python
        User.disable_mfa()
        ```
        """

        user_uuid = User.details(user=user, otp=otp)["uuid"]
        url = f"/user/mfa/{user_uuid}/disable"
        response = Client._delete_data(
            relative_url=check_and_append_otp_query_param(url, otp)
        )

        return pd.DataFrame(response, index=[0])

    @staticmethod
    def enable_sso(
        sso_provider: str, sso_email: str, otp: Optional[str] = None
    ) -> pd.DataFrame:
        """Enable Single sign-on (SSO) for the user

        Args:
            sso_provider: Name of the Single sign-on (SSO) identity provider
            sso_email: Email id going to be used for SSO authentication
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if the MFA is enabled for your account.

        Returns:
            A pandas DataFrame encapsulating the SSO enabled user details

        An example to enable Single sign-on (SSO):

        ```python
        User.enable_sso(sso_provider, sso_email)
        ```
        """
        response = Client._post_data(
            relative_url=f"/user/sso/enable",
            data=dict(sso_provider=sso_provider, sso_email=sso_email, otp=otp),
        )
        success_msg = f"Single sign-on (SSO) is successfully enabled for {sso_provider}. Please use {response['sso_email']} as the email address while authenticating with {sso_provider}."

        return success_msg

    @staticmethod
    def disable_sso(
        sso_provider: str,
        user: Optional[str] = None,
        otp: Optional[str] = None,
    ) -> pd.DataFrame:
        """Disable Single sign-on (SSO) for the user

        Please do not pass the user parameter unless you are a super user. Only
        a super user can disable SSO for other users.

        Args:
            sso_provider: The name of the Single sign-on (SSO) provider you want to disable.
            user: Account user_uuid/username to disable SSO. If not passed, then the default
                value **None** will be used to disable SSO for the currently logged-in user.
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if the MFA is enabled for your account.

        Returns:
            A pandas DataFrame encapsulating the SSO disabled user details

        Raises:
            ValueError: If a non-super user tries to disable SSO for other users
            ValueException: If the OTP is invalid.
            ConnectionError: If the server address is invalid or not reachable.

        An example to disable SSO:

        ```python
        User.disable_sso(sso_provider="provider_name")
        ```
        """

        user_uuid = User.details(user=user, otp=otp)["uuid"]
        url = f"/user/sso/{user_uuid}/disable/{sso_provider}"
        response = Client._delete_data(
            relative_url=check_and_append_otp_query_param(url, otp)
        )

        success_msg = f"Single sign-on (SSO) is successfully disabled for {response['sso_provider']}."

        return success_msg
