# AUTOGENERATED! DO NOT EDIT! File to edit: ../../notebooks/API_User.ipynb.

# %% auto 0
__all__ = ['User']

# %% ../../notebooks/API_User.ipynb 4
from typing import *

# %% ../../notebooks/API_User.ipynb 5
import os

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

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

from airt.components.client import Client

# %% ../../notebooks/API_User.ipynb 7
logger = get_logger(__name__)

# %% ../../notebooks/API_User.ipynb 10
class User:
    """A class for creating, managing, and updating users on the server.

    The **User** class has two types of methods:

    * Methods for creating and managing users.
    * Method for updating and adding additional security to user accounts.

    Methods such as `create`, `enable`, `disable`, and `ls` can be used to manage user accounts on the server, but access to them requires super user privileges.

    The remaining methods do not require super user privileges and are used to update/additionally secure user accounts.

    In addition to the regular authentication with credentials, the users can enable multi-factor authentication (MFA) and single sign-on (SSO) for their accounts.

    To help protect your account, we recommend that you enable multi-factor authentication (MFA). MFA provides additional security by requiring you to provide unique verification code (OTP) in addition to your regular sign-in credentials when performing critical operations.

    Your account can be configured for MFA in just two easy steps:

    - To begin, you need to enable MFA for your account by calling the `enable_mfa` method, which will generate a QR code. You can then scan the QR code with an authenticator app, such as Google Authenticator and follow the on-device instructions to finish the setup in your smartphone.

    - Finally, activate MFA for your account by calling `activate_mfa` and passing the dynamically generated six-digit verification code from your smartphone's authenticator app.

    Single sign-on (SSO) can be enabled for your account in three simple steps:

    - Enable the SSO for a provider by calling the `enable_sso` method with the SSO provider name and an email address. At the moment, we only support "google" and "github" as SSO providers. We intend to support additional SSO providers in future releases.

    - Before you can start generating new tokens with SSO, you must first authenticate with the SSO provider. Call the get_token with the same SSO provider you have enabled in the step above to generate an SSO authorization URL. Please copy and paste it into your preferred browser and complete the authentication process with the SSO provider.

    - After successfully authenticating with the SSO provider, call the `Client.set_sso_token` method to generate a new token and use it automatically in all future interactions with the airt server.

    Here's an example of using the User class's methods to display the logged-in user's uuid

    Example:
        ```python
        # Importing necessary libraries
        from  airt.client import User, Client

        # Authenticate
        Client.get_token(username="{fill in username}", password="{fill in password}")

        # Display the uuid of logged-in user
        user_details = User.details()
        print(user_details["uuid"])
        ```
    """

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

    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,
        phone_number: Optional[str] = None,
        is_phone_number_verified: 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.
            phone_number: The registered phone number of the user. The phone number should follow the pattern of country
                code followed by your phone number. For example, **440123456789, +440123456789, 00440123456789, +44 0123456789,
                and (+44) 012 345 6789** are all valid formats for registering a UK phone number.
            is_phone_number_verified: Flag to indicate the phone number registration 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
        self.phone_number = phone_number
        self.is_phone_number_verified = is_phone_number_verified

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

        To access this method, you must have super user privileges.

        Args:
            username: The new user's username. The username must be unique or an exception will be thrown.
            first_name: The new user's first name.
            last_name: The new user's last name.
            email: The new user's email. The email must be unique or an exception will be thrown.
            password: The new user's password.
            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.
            phone_number: Phone number to be added to the user account. The phone number should follow the pattern of the country
                code followed by your phone number. For example, **440123456789, +440123456789, 00440123456789, +44 0123456789,
                and (+44) 012 345 6789** are all valid formats for registering a UK phone number.
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if you have activated the MFA 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.
            ValueError: If the OTP is invalid.
            ValueError: If the username or email is already present in the server.

        Below is an example of creating a new user

        Example:
            ```python

            # Importing necessary libraries
            from  airt.client import Client, User

            # Authenticate with super user privileges
            Client.get_token(
                username="{fill in super_user_username}",
                password="{fill in super_user_password}"
            )

            # Details required to create a new user
            username = "{fill in username}"
            first_name = "{fill in first_name}"
            last_name = "{fill in last_name}"
            email = "{fill in email}"
            password = "{fill in password}"
            super_user = "{fill in super_user}"
            subscription_type = "{fill in subscription_type}"

            # Create a new user. To access this method, you must have super user privileges.
            new_user = User.create(
                username=username,
                first_name=first_name,
                last_name=last_name,
                email=email,
                password=password,
                super_user=super_user,
                subscription_type=subscription_type,
            )

            # Display the details of the newly created user
            print(new_user)

            # An exception will be raised if you attempt to create a new user
            # with an already-used username or email address.
            try:
                User.create(
                    username=username,
                    first_name=first_name,
                    last_name=last_name,
                    email=email,
                    password=password,
                    super_user=super_user,
                    subscription_type=subscription_type,
                )
                print("Should not print this, the above line should raise an exception")
                raise RuntimeException()

            except ValueError as e:
                print("Expected to fail, everything is fine")
            ```
        """

        if phone_number is not None:
            phone_number = standardize_phone_number(phone_number)

        req_json = dict(
            username=username,
            first_name=first_name,
            last_name=last_name,
            email=email,
            subscription_type=subscription_type,
            super_user=super_user,
            password=password,
            phone_number=phone_number,
            otp=otp,
        )

        response = Client._post_data(relative_url=f"/user/", json=req_json)

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

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

        To access this method, you must have super user privileges.

        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.

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

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

        An example of displaying the details of all active users

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Authenticate with super user privileges
            Client.get_token(
                username="{fill in super_user_username}",
                password="{fill in super_user_password}"
            )

            # Display the details of all active users
            # Set the disabled parameter to True to display the details of inactive users
            ux = User.ls()
            print(User.as_df(ux))
            ```
        """
        users = Client._get_data(
            relative_url=f"/user/?disabled={disabled}&offset={offset}&limit={limit}"
        )

        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"],
                phone_number=user["phone_number"],
                is_phone_number_verified=user["is_phone_number_verified"],
            )
            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 of displaying the details of all active users

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Authenticate with super user privileges
            # Only super users can get the list of available users
            Client.get_token(
                username="{fill in super_user_username}",
                password="{fill in super_user_password}"
            )

            # Display the details of all active users
            # Set the disabled parameter to True to display the details of inactive users
            ux = User.ls()
            print(User.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.

        To access this method, you must have super user privileges.

        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 you have activated the MFA for your account.

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

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

        Here's an example to disable a user

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Authenticate with super user privileges
            Client.get_token(
                username="{fill in super_user_username}",
                password="{fill in super_user_password}"
            )

            # Optional Step: For demonstration purpose, create a new user
            username = "{fill in username}"
            User.create(
                username=username,
                first_name="{fill in first_name}",
                last_name="{fill in last_name}",
                email="{fill in email}",
                password="{fill in password}",
                subscription_type="{fill in subscription_type}",
            )

            # Display the details of the user you want to disable using their username/uuid
            print(User.details(username))

            # Disable the user
            # To disable multiple users, pass a list of username/uuid
            User.disable(user=username)

            # Check whether the user has been disabled
            # The disabled flag should be set to True, if the disable was sucessful
            print(User.details(username))
            ```
        """
        _users = user if isinstance(user, list) else [user]
        response_list = []
        for user in _users:
            user_uuid = User.details(user=user)["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.

        To access this method, you must have super user privileges.

        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 you have activated the MFA for your account.

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

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

        Here's an example to enable a user

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Authenticate with super user privileges
            Client.get_token(
                username="{fill in super_user_username}",
                password="{fill in super_user_password}"
            )

            # Optional Step: For demonstration purpose, create a new user and disable them
            username = "{fill in username}"
            User.create(
                username=username,
                first_name="{fill in first_name}",
                last_name="{fill in last_name}",
                email="{fill in email}",
                password="{fill in password}",
                subscription_type="{fill in subscription_type}",
            )
            User.disable(user=username)

            # Display the details of the user you want to enable using their username/uuid
            print(User.details(username))

            # Enable the user
            # To enable multiple users, pass a list of username/uuid
            User.enable(user=username)

            # Check whether the user has been enabled
            # The disabled flag should be set to False, if the enable was sucessful
            print(User.details(username))

            ```
        """

        _users = user if isinstance(user, list) else [user]
        response_list = []
        for user in _users:
            user_uuid = User.details(user=user)["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) -> 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.

        Returns:
            A dict containing the details of the user

        Raises:
            ValueError: If the user_uuid/username is invalid or the user have insufficient permission to access other user's data
            ConnectionError: If the server address is invalid or not reachable.

        An example of displaying the logged-in user's uuid

        Example:
            ```python

            # Importing necessary libraries
            from  airt.client import User, Client

            # Authenticate
            Client.get_token(username="{fill in username}", password="{fill in password}")

            # Display the uuid of logged-in user
            # If you are a super user, you can view the details of other users by
            # passing their uuid/username in the user parameter.
            user_details = User.details()
            print(user_details["uuid"])

            # If a Non-super user tries to access other user's detail,
            # an exception will be thrown.
            try:
                User.details(user="some_other_username")
                print("Should not print this, the above line should raise an exception")
                raise RuntimeException()

            except ValueError as e:
                print("Expected to fail, everything is fine")
            ```
        """
        relative_url = (
            f"/user/details?user_uuid_or_name={user}"
            if user is not None
            else f"/user/details"
        )
        return Client._get_data(relative_url=relative_url)

    @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,
        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.
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if you have activated the MFA for your account.

        Returns:
            A pandas DataFrame encapsulating the updated user details.

        Raises:
            ValueError: If the user_uuid/username is invalid or the user have insufficient permission to access other user's data
            ValueError: If the OTP is invalid.
            ConnectionError: If the server address is invalid or not reachable.

        Here's an example of updating the email address of the logged-in user

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import User, Client

            # Authenticate
            Client.get_token(username="{fill in username}", password="{fill in password}")

            # Display the registered email address of the logged-in user
            print(User.details()["email"])

            # Update the logged-in user's email address
            # If you are a super user, you can update the details of other users by
            # passing their uuid/username in the user parameter
            email = "{fill in new_email}"
            User.update(email=email)

            # Check whether the email address has been updated for the logged-in user
            print(User.details()["email"])

            # If you try to use an already used email address, an exception will be raised.
            try:
                User.update(email=email)
                print("Should not print this, the above line should raise an exception")
                raise RuntimeException()

            except ValueError as e:
                print("Expected to fail, everything is fine")
            ```
        """
        user_uuid = User.details(user=user)["uuid"]
        req_json = dict(
            username=username,
            first_name=first_name,
            last_name=last_name,
            email=email,
            otp=otp,
        )

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

        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 you have activated the MFA 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. To finish the setup on your smartphone, scan the
        QR code with an authenticator app such as Google Authenticator and follow the on-device
        instructions. After you've completed the setup, call the `activate_mfa` method and pass the
        current OTP from the authenticator application to verify and enable MFA for your account

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

        Returns:
            The generated QR code

        Here's an example to enable and activate MFA for the user

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import User, Client

            # Authenticate
            Client.get_token(username="{fill in username}", password="{fill in password}")

            # Check the current MFA status
            print(User.details()["is_mfa_active"])

            # Enable MFA for the user
            # The line below will generate a QR code. To finish the setup on your smartphone,
            # scan the QR code with an authenticator app like Google Authenticator and
            # follow the on-device instructions
            User.enable_mfa()

            # After you've completed the setup, enter the current OTP from the authenticator
            # app to verify and enable MFA for your account
            User.activate_mfa(otp="{fill in otp}")

            # Check the current MFA status
            # The is_mfa_active flag should be set to True, if the setup is successful
            print(User.details()["is_mfa_active"])
            ```
        """
        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

        Adding MFA to your account is a two-step process. To begin, you must enable MFA for your account
        by calling the `enable_mfa` method, then call the `activate_mfa` method and pass the current OTP
        from the authenticator application to verify and activate MFA for your account.

        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

        Here's an example to enable and activate MFA for the user

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import User, Client

            # Authenticate
            Client.get_token(username="{fill in username}", password="{fill in password}")

            # Check the current MFA status
            print(User.details()["is_mfa_active"])

            # Enable MFA for the user
            # The line below will generate a QR code. To finish the setup on your smartphone,
            # scan the QR code with an authenticator app like Google Authenticator and
            # follow the on-device instructions
            User.enable_mfa()

            # After you've completed the setup, enter the current OTP from the authenticator
            # app to verify and enable MFA for your account
            User.activate_mfa(otp="{fill in otp}")

            # Check the current MFA status
            # The is_mfa_active flag should be set to True, if the setup is successful
            print(User.details()["is_mfa_active"])
            ```
        """
        response = Client._post_data(
            relative_url="/user/mfa/activate",
            json=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

        We currently support two types of OTPs for disabling multi-factor authentication on your account.

        If you have access to the authenticator app, enter the app's dynamically generated six-digit verification
        code(OTP). If you don't have access to the authentication app, you can have an OTP sent to your registered
        phone number via SMS.

        To receive OTP via SMS, first call the `send_sms_otp` method, which will send the OTP to your registered
        phone number. Once you have the OTP, call the `disable_mfa` method to deactivate MFA for your account.

        Currently, we only support the above two methods for disabling MFA. If you do not have access to the authenticator
        app or your registered phone number, please contact your administrator.

        !!! note

            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 or
                the OTP you have received via SMS.

        Returns:
            A pandas DataFrame encapsulating the MFA disabled user details

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

        Here's an example of disabling MFA for the currently logged in user using the verification code generated by the authentication application.
        The example assumes that you have already activated the MFA on your account and have access to the authentication application.

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Optional Step: Skip this step if you've already logged-in
            # Authenticate. Pass the current OTP from the authenticator app below
            otp="{fill in otp}"
            Client.get_token(
                username="{fill in username}",
                password="{fill in password}",
                otp=otp
            )

            # Check the current MFA status
            print(User.details()["is_mfa_active"])

            # Disable MFA for the user
            User.disable_mfa(otp=otp)

            # Check the current MFA status
            # The is_mfa_active flag should be set to False, if the disable was successful
            print(User.details()["is_mfa_active"])
            ```

        Here's an example of disabling MFA for the currently logged in user using the SMS OTP. The example assumes that you have
        already registered and validated your phone number on our servers.

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Optional Step: Request OTP via SMS to authenticate
            # If you've already logged in, you can skip the two optional steps
            # If you've already have a valid token, skip the below optional step
            # and call Client.set_token instead of Client.get_token
            username="{fill in username}"
            User.send_sms_otp(
                username=username,
                message_template_name="get_token" # Don't change the message_template_name
            )

            # Optional Step: Authenticate using SMS OTP
            # The send_sms_otp method will send the OTP via SMS to the registered
            # phone number, which you must fill below
            password="{fill in password}"
            otp="{fill in otp}"
            Client.get_token(username=username, password=password, otp=otp)

            # Check the current MFA status
            print(User.details()["is_mfa_active"])

            # Request OTP via SMS to disable MFA
            User.send_sms_otp(
                username=username,
                message_template_name="disable_mfa" # Don't change the message_template_name
            )

            # The send_sms_otp method will send the OTP via SMS to the registered
            # phone number, which you must fill below
            User.disable_mfa(otp="{fill in otp}")

            # Check the current MFA status
            # The is_mfa_active flag should be set to False, if the disable was successful
            print(User.details()["is_mfa_active"])
            ```
        """

        user_uuid = User.details(user=user)["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) -> str:
        """Enable Single sign-on (SSO) for the user

        Args:
            sso_provider: Name of the Single sign-on (SSO) identity provider.
                At present, the API only supports **"google"** and **"github"** as valid SSO identity providers.
            sso_email: Email id to be used for SSO authentication
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if you have activated the MFA for your account.

        Returns:
            A pandas DataFrame encapsulating the user details

        Here's an example of authenticating with Single sign-on (SSO) using google and
        setting the newly generated token to interact with the airt service.

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import User, Client

            # Authenticate
            Client.get_token(username="{fill in username}", password="{fill in password}")

            # Enable single sign-on (SSO) and use google as the provider
            sso_provider="google"
            sso_email="{fill in sso_email}"
            User.enable_sso(sso_provider=sso_provider, sso_email=sso_email)

            # Authenticate using Single sign-on (SSO)
            # To generate a token using SSO, you must first authenticate with the provider.
            # The command below will generate an authorization URL for you.
            # Please copy and paste it into your preferred browser and complete the
            # SSO provider authentication within 10 minutes. Otherwise, the SSO login
            # will time out and you will need to call the get_token method again.
            sso_url = Client.get_token(sso_provider=sso_provider)
            print(sso_url)

            # Once the provider authentication is successful, call the below method to
            # set the generated token
            Client.set_sso_token()

            # If set_sso_token fails, the line below will throw an error.
            print(User.details())
            ```
        """
        response = Client._post_data(
            relative_url=f"/user/sso/enable",
            json=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,
    ) -> str:
        """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.
                At present, the API only supports **"google"** and **"github"** as valid SSO identity providers.
            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 you have activated the MFA 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
            ValueError: If the OTP is invalid.
            ConnectionError: If the server address is invalid or not reachable.

        Here's an example to disable the Single sign-on (SSO)

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import User, Client

            # Authenticate
            Client.get_token(username="{fill in username}", password="{fill in password}")

            # Optional Step: For demonstration purpose, enable Single sign-on (SSO)
            # for the user
            sso_provider="{fill in sso_provider}"
            sso_email="{fill in sso_email}"
            User.enable_sso(sso_provider=sso_provider, sso_email=sso_email)
            sso_url = Client.get_token(sso_provider=sso_provider) # Authenticate using SSO
            print(sso_url)
            Client.set_sso_token() # Set SSO token

            # Disable the Single sign-on (SSO) for the provider
            print(User.disable_sso(sso_provider=sso_provider))

            # If you try to disable an already disabled SSO provider, an exception
            # will be raised
            try:
                User.disable_sso(sso_provider=sso_provider)
                print("Should not print this, the above line should raise an exception")
                raise RuntimeException()

            except ValueError as e:
                print("Expected to fail, everything is fine")
            ```
        """

        user_uuid = User.details(user=user)["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

    @staticmethod
    def register_phone_number(
        phone_number: Optional[str] = None,
        otp: Optional[str] = None,
    ) -> Dict[str, Union[str, bool]]:
        """Register a phone number

        Registering your phone number will help you to regain access in case you forget your password and cannot access
        your account. To receive the OTP via SMS, you need to register and validate your phone number. Calling this
        method will send an OTP via SMS to the phone number and you need to call the `validate_phone_number` method
        with the OTP you have received to complete the registration and validation process.

        Args:
            phone_number: Phone number to register. The phone number should follow the pattern of the country
                code followed by your phone number. For example, **440123456789, +440123456789,
                00440123456789, +44 0123456789, and (+44) 012 345 6789** are all valid formats for registering a
                UK phone number. If the phone number is not passed in the arguments, then the OTP will be sent to
                the phone number that was already registered to the user's account.
            otp: Dynamically generated six-digit verification code from the authenticator app. Please pass this
                parameter only if you have activated the MFA for your account.

        Returns:
            A dict containing the updated user details

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

        Here's an example of registering and validating a new phone number for the currently logged-in user

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Authenticate
            Client.get_token(username="{fill in username}", password="{fill in password}")

            # Display the phone number that is currently registered
            # If no phone number has been registered, None will be displayed
            print(User.details()["phone_number"])

            # Register a new phone number.
            # If you only want to validate an existing phone number, call the
            # method below without the phone_number parameter
            User.register_phone_number(phone_number="{fill in phone_number}")

            # The above method will send the OTP via SMS to the specified phone number,
            # which you must enter below to complete the registration process
            User.validate_phone_number(otp="{fill in otp}")

            # Check whether the phone number has been updated and verified
            # The is_phone_number_verified flag should be set to True, if the
            # registration is successful
            user_details = User.details()
            print(user_details["phone_number"])
            print(user_details["is_phone_number_verified"])
            ```
        """
        if phone_number is not None:
            phone_number = standardize_phone_number(phone_number)

        req_json = dict(phone_number=phone_number, otp=otp)
        return Client._post_data(
            relative_url="/user/register_phone_number", json=req_json
        )

    @staticmethod
    def validate_phone_number(
        otp: Optional[str] = None,
    ) -> Dict[str, Union[str, bool]]:
        """Validate a registered phone number

        Please call the `register_phone_number` method to get the OTP via SMS and then call this method
        with the OTP to complete the registration and validation process.

        Args:
            otp: The OTP you have received on your registered phone number.

        Returns:
            A dict containing the updated user details

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

        Here's an example of registering and validating a new phone number for the currently logged-in user

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Authenticate
            Client.get_token(username="{fill in username}", password="{fill in password}")

            # Display the phone number that is currently registered
            # If no phone number has been registered, None will be displayed
            print(User.details()["phone_number"])

            # Register a new phone number.
            # If you only want to validate an existing phone number, call the
            # method below without the phone_number parameter
            User.register_phone_number(phone_number="{fill in phone_number}")

            # The above method will send the OTP via SMS to the specified phone number,
            # which you must enter below to complete the registration process
            User.validate_phone_number(otp="{fill in otp}")

            # Check whether the phone number has been updated and verified
            # The is_phone_number_verified flag should be set to True, if the
            # registration is successful
            user_details = User.details()
            print(user_details["phone_number"])
            print(user_details["is_phone_number_verified"])
            ```
        """
        url = "/user/validate_phone_number"

        return Client._get_data(relative_url=check_and_append_otp_query_param(url, otp))

    @staticmethod
    def send_sms_otp(username: str, message_template_name: str) -> str:
        """Send OTP via SMS to the user's registered phone number

        This method does not require a login, and you should only use it to reset your password,
        disable MFA, or generate a new token using SMS OTP.

        Calling this method will only send an OTP to your registered phone number via SMS. Following this method
        call, you should explicitly call `reset_password`, `disable_mfa`, or `Client.get_token` to complete
        the operation with the SMS OTP.

        Please remember to pass a valid message_template_name along with the request. At present, the API
        supports **"reset_password"**, **"disable_mfa"** and **"get_token"** as valid message templates.

        Args:
            username: Account username to send the OTP
            message_template_name: The message template to use while sending the OTP via SMS. At present,
                the API supports **"reset_password"**, **"disable_mfa"** and **"get_token"** as valid message templates

        Returns:
            The SMS status message

        Here's an example of a Non-MFA user resetting their password using the SMS OTP. The example assumes that you have
        already registered and validated your phone number on our servers.

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Request OTP via SMS to the registered phone number
            # Please do not change the message_template_name
            username="{fill in username}"
            User.send_sms_otp(username=username, message_template_name="reset_password")

            # The above method will send the OTP via SMS to the registered phone number,
            # which you must fill below along with your new password
            new_password = "{fill in new_password}"
            otp = "{fill in otp}" # OTP received via SMS

            # Reset the password
            User.reset_password(username=username, new_password=new_password, otp=otp)

            # Authenticate using the new credentials
            # MFA users must pass the otp generated by the authenticator app below
            Client.get_token(username=username, password=new_password)

            # Check if get_token is successful
            print(User.details())
            ```

        Here's an example of how to disable MFA with SMS OTP, assuming you've already registered
        and validated your phone number on our servers but don't have a valid token.

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Optional Step: Request OTP via SMS to authenticate
            # If you've already logged in, you can skip the two optional steps
            # If you've already have a valid token, skip the below optional step
            # and call Client.set_token instead of Client.get_token
            username="{fill in username}"
            User.send_sms_otp(
                username=username,
                message_template_name="get_token" # Don't change the message_template_name
            )

            # Optional Step: Authenticate using SMS OTP
            # The send_sms_otp method will send the OTP via SMS to the registered
            # phone number, which you must fill below
            password="{fill in password}"
            otp="{fill in otp}"
            Client.get_token(username=username, password=password, otp=otp)

            # Check the current MFA status
            print(User.details()["is_mfa_active"])

            # Request OTP via SMS to disable MFA
            User.send_sms_otp(
                username=username,
                message_template_name="disable_mfa" # Don't change the message_template_name
            )

            # The send_sms_otp method will send the OTP via SMS to the registered
            # phone number, which you must fill below
            User.disable_mfa(otp="{fill in otp}")

            # Check the current MFA status
            # The is_mfa_active flag should be set to False, if the disable was successful
            print(User.details()["is_mfa_active"])
            ```
        """
        url = f"/user/send_sms_otp?username={username}&message_template_name={message_template_name}"

        return Client._get_data(relative_url=url)

    @staticmethod
    def reset_password(username: str, new_password: str, otp: str) -> str:
        """Resets the password of an account either using a TOTP or SMS OTP.

        We currently support two types of OTPs to reset the password for your account and you don't have to be logged
        in to call this method

        If you have already activated the MFA for your account, then you can either pass the dynamically generated
        six-digit verification code from the authenticator app (TOTP) or you can also request an OTP via SMS to your registered phone number.

        If the MFA is not activated already, then you can only request the OTP via SMS to your registered phone number.

        To get OTP by SMS, you must first call `send_sms_otp` method which will send the OTP to your registered
        phone number. Once you have the OTP with you, then call this method with the OTP to reset your password.

        Currently, we only support the above two methods for resetting the password. In case, you don't have MFA enabled or don't
        have access to your registered phone number, please contact your administrator.

        Args:
            username: Account username to reset the password
            new_password: New password to set for the account
            otp: Dynamically generated six-digit verification code from the authenticator app or the OTP you have received
                via SMS.

        Returns:
            The password reset status message

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

        Here's an example for resetting the password using the verification code generated by the authentication application.
        The example assumes that you have already activated the MFA on your account and have access to the authentication application.

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Details required to reset the password
            username = "{fill in username}"
            new_password = "{fill in new_password}"
            otp = "{fill in otp}" # OTP generated by the authenticator app

            # Reset the password
            User.reset_password(username=username, new_password=new_password, otp=otp)

            # Authenticate using the new credentials
            Client.get_token(username=username, password=new_password, otp=otp)

            # Check if get_token is successful
            print(User.details())
            ```

        Here's an example of a Non-MFA user resetting their password using the SMS OTP. The example assumes that you have already registered
        and validated your phone number on our servers.

        Example:
            ```python
            # Importing necessary libraries
            from  airt.client import Client, User

            # Request OTP via SMS to the registered phone number
            # Please do not change the message_template_name
            username="{fill in username}"
            User.send_sms_otp(username=username, message_template_name="reset_password")

            # The above method will send the OTP via SMS to the registered phone number,
            # which you must fill below along with your new password
            new_password = "{fill in new_password}"
            otp = "{fill in otp}" # OTP received via SMS

            # Reset the password
            User.reset_password(username=username, new_password=new_password, otp=otp)

            # Authenticate using the new credentials
            # MFA users must pass the otp generated by the authenticator app below
            Client.get_token(username=username, password=new_password)

            # Check if get_token is successful
            print(User.details())
            ```
        """
        req_json = dict(username=username, new_password=new_password, otp=otp)
        return Client._post_data(relative_url="/user/reset_password", json=req_json)  # type: ignore
