import requests
import urllib.parse


class FangCloudServicesAPI:
    """
    Welcome to the FangCloudServices API!
    
    If you have any questions or encounter any issues, please do not hesitate to reach out.
    
    To obtain the credentials to use this API, please see the *Acquiring Project API Credentials* section of the *Setup Guide*.
    
    More API functionality will be coming soon.
    """

    url_base = "https://fangcloudservices.pythonanywhere.com/api/v1"

    def __init__(self, access_token):
        self.headers = {'Authorization': 'Bearer {}'.format(access_token)}

    def _check_status(self, r: requests.Response, retry: callable):
        return r.json()
        
    def _url_encode(self, text: str) -> str:
        return urllib.parse.quote_plus(str(text))
    
    def validate_token(self, token, minified) -> dict:
        """
        Takes the specified access token which is provided by your API client, and returns the data related to the user's account.
        
        This endpoint will also detect expired and invalid tokens.
        :param token: The token you wish to validate
        :param minified: If a minified response should be created
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "GET", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/token?token={}&minified={}".format(self._url_encode(token), self._url_encode(minified)), 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.validate_token(**local_vars))
        
    def list_directories(self) -> dict:
        """
        Lists all of the directories in the project-level file storage.
        
        **Note**: Directories for individual users will appear automatically when a file is uploaded to their location, or when a list of their directory is requested.
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "GET", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/file", 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.list_directories(**local_vars))
        
    def list_user_directory(self) -> dict:
        """
        Retrieves a list of files stored under the specified user.
        
        Note that the `user_id` parameter can be replaced with `project` to interact with the project specific directory.
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "GET", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/file/[user_id]", 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.list_user_directory(**local_vars))
        
    def upload_file(self) -> dict:
        """
        Upload many files to a single user which is specified in the URL.
        
        Note that the `user_id` parameter can be replaced with `project` to interact with the project specific directory.
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        raise NotImplementedError('The requested endpoint is not ready for use')

    def get_file(self) -> dict:
        """
        Retrieves the contents of the specified file.
        
        Note that the `user_id` parameter can be replaced with `project` to interact with the project specific directory.
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "GET", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/file/[user_id]/[filename]", 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.get_file(**local_vars))
        
    def upload_file(self) -> dict:
        """
        Uploads the provided file with the specified filename. If many files are uploaded, only one will be saved.
        
        Note that the `user_id` parameter can be replaced with `project` to interact with the project specific directory.
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        raise NotImplementedError('The requested endpoint is not ready for use')

    def update_file(self) -> dict:
        """
        Writes the raw data sent in the request to the specified file. The old file will be overwritten and all previous content will be lost. This action can not be undone.
        
        If the file does not exist, a new one will be created
        
        Note that the `user_id` parameter can be replaced with `project` to interact with the project specific directory.
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        headers["Content-Type"] = "text/plain"
        r = requests.request(
            "PUT", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/file/[user_id]/[filename]", 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.update_file(**local_vars))
        
    def remove_file(self) -> dict:
        """
        Removes the specified file. This action can not be undone.
        
        Note that the `user_id` parameter can be replaced with `project` to interact with the project specific directory.
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "DELETE", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/file/[user_id]/[filename]", 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.remove_file(**local_vars))
        
    def get_all_users(self) -> dict:
        """
        Returns a list of every user. Includes connected accounts, email addresses, and their user role.
        
        **WARNING**: Due to the large volume of data which is pulled by this request, the response time can be quite high. It is advised to get users individually.
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "GET", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/user", 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.get_all_users(**local_vars))
        
    def get_user(self, id) -> dict:
        """
        This endpoint retrieves a single user who has the specified ID.
        :param id: 
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "GET", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/user?id={}".format(self._url_encode(id)), 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.get_user(**local_vars))
        
    def update_user(self, id, data, username, primary_email, user_role, active) -> dict:
        """
        Updates the properties of the specified user. Note that sending up the `data` JSON blob will overwrite the existing data so be sure to merge the data before sending.
        
        **Note** that this action can not be undone.
        
        If the requested `user_role` or `primary_email` IDs do not correspond to an existing user role or email address, you will receive a 404 error and the update will not be made.
        
        Finally, unlike most of the other endpoints, there are no mandatory values in the JSON body, so you only need to send what you want to change.
        :param id: 
        :param data:
        :param username:
        :param primary_email:
        :param user_role:
        :param active:
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        headers["Content-Type"] = "application/json"
        r = requests.request(
            "PUT", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/user?id={}".format(self._url_encode(id)), 
            headers=headers,
            json={
                "active": active,
                "data": data,
                "primary_email": primary_email,
                "user_role": user_role,
                "username": username
            }
        )
        
        return self._check_status(r, lambda: self.update_user(**local_vars))
        
    def get_all_email_addresses(self, user) -> dict:
        """
        Retrieves the email addresses linked to a specified user.
        :param user: The ID of the user
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "GET", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/email?user={}".format(self._url_encode(user)), 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.get_all_email_addresses(**local_vars))
        
    def create_email_address(self, user, email) -> dict:
        """
        Links the specified email address to the specified user.
        
        This will cause the confirmation email to be sent to the new email address.
        :param user:
        :param email:
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        headers["Content-Type"] = "application/json"
        r = requests.request(
            "POST", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/email", 
            headers=headers,
            json={
                "email": email,
                "user": user
            }
        )
        
        return self._check_status(r, lambda: self.create_email_address(**local_vars))
        
    def update_email_address(self, id, user, email) -> dict:
        """
        Updates a user's existing email address
        :param id: The ID of the email address
        :param user:
        :param email:
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        headers["Content-Type"] = "application/json"
        r = requests.request(
            "PUT", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/email?id={}".format(self._url_encode(id)), 
            headers=headers,
            json={
                "email": email,
                "user": user
            }
        )
        
        return self._check_status(r, lambda: self.update_email_address(**local_vars))
        
    def remove_email_address(self, id, user) -> dict:
        """
        
        :param id: The ID of the email address to remove
        :param user: The ID of the user ascociated with the email address
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "DELETE", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/email?id={}&user={}".format(self._url_encode(id), self._url_encode(user)), 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.remove_email_address(**local_vars))
        
    def get_external_accounts(self) -> dict:
        """
        This endpoint lists the external accounts your project allows users to log in with
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "GET", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/external_account", 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.get_external_accounts(**local_vars))
        
    def connect_new_external_account(self, id) -> dict:
        """
        Allow users to log in with an external account. If you wish to specify your own credentials for the authentication process, include them in the request JSON as `credentials`.
        
        Note that we will never show you your credentials after you have submitted them.
        :param id: The ID of the platform to integrate with
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "POST", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/external_account?id={}".format(self._url_encode(id)), 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.connect_new_external_account(**local_vars))
        
    def update_account_connection(self, id, credentials) -> dict:
        """
        This endpoint allows you to update and change the credentials associated with a linked account.
        
        Note that we will never show you your credentials after you have submitted them.
        
        If you wish to use the default credentials for the authentication process, omit the `credentials` property in the request JSON
        :param id: 
        :param credentials:
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        headers["Content-Type"] = "application/json"
        r = requests.request(
            "PUT", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/external_account?id={}".format(self._url_encode(id)), 
            headers=headers,
            json={
                "credentials": credentials
            }
        )
        
        return self._check_status(r, lambda: self.update_account_connection(**local_vars))
        
    def unlink_account(self, id) -> dict:
        """
        This endpoint allows you to unlink a third party linked account.
        
        **WARNING**! Any user who previously logged in with the unlinked account will no longer be able to log in, unless they have linked another means of log in.
        
        Be sure you know what you are doing before performing this action. This **CAN NOT** be undone!
        :param id: 
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "DELETE", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/external_account?id={}".format(self._url_encode(id)), 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.unlink_account(**local_vars))
        
    def get_requirements(self) -> dict:
        """
        Retrieves the password requirements set for your users when they sign up.
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "GET", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/password_requirements", 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.get_requirements(**local_vars))
        
    def change_requirements(self, min_length, numbers, special_chars, upper_lower_chars, store_last_passwords) -> dict:
        """
        Allows you to change the password requirements which your users must meet when creating a new account.
        :param min_length:
        :param numbers:
        :param special_chars:
        :param upper_lower_chars:
        :param store_last_passwords:
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        headers["Content-Type"] = "application/json"
        r = requests.request(
            "PUT", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/password_requirements", 
            headers=headers,
            json={
                "min_length": min_length,
                "numbers": numbers,
                "special_chars": special_chars,
                "store_last_passwords": store_last_passwords,
                "upper_lower_chars": upper_lower_chars
            }
        )
        
        return self._check_status(r, lambda: self.change_requirements(**local_vars))
        
    def get_settings(self) -> dict:
        """
        Retrieves your project settings
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "GET", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project", 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.get_settings(**local_vars))
        
    def change_settings(self, id, default_data, default_user_role, name, privacy_policy, terms_of_service) -> dict:
        """
        Changes your project settings
        :param id: 
        :param default_data:
        :param default_user_role:
        :param name:
        :param privacy_policy:
        :param terms_of_service:
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        headers["Content-Type"] = "application/json"
        r = requests.request(
            "PUT", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project?id={}".format(self._url_encode(id)), 
            headers=headers,
            json={
                "default_data": default_data,
                "default_user_role": default_user_role,
                "name": name,
                "privacy_policy": privacy_policy,
                "terms_of_service": terms_of_service
            }
        )
        
        return self._check_status(r, lambda: self.change_settings(**local_vars))
        
    def get_scopes(self) -> dict:
        """
        Retrieves the full list of scopes available to your account.
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "GET", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/scope", 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.get_scopes(**local_vars))
        
    def create_scope(self, tag, display) -> dict:
        """
        Creates a new scope. Note that no user roles will be able to access the scope until you allow access.
        :param tag:
        :param display:
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        headers["Content-Type"] = "application/json"
        r = requests.request(
            "POST", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/scope", 
            headers=headers,
            json={
                "display": display,
                "tag": tag
            }
        )
        
        return self._check_status(r, lambda: self.create_scope(**local_vars))
        
    def update_scope(self, id, tag, display) -> dict:
        """
        Update the tag and display name of a scope.
        
        Changing the tag may have unexpected consequences when authenticating, however, user roles will keep their access to the scopes.
        :param id: The ID of the scope
        :param tag:
        :param display:
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        headers["Content-Type"] = "application/json"
        r = requests.request(
            "PUT", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/scope?id={}".format(self._url_encode(id)), 
            headers=headers,
            json={
                "display": display,
                "tag": tag
            }
        )
        
        return self._check_status(r, lambda: self.update_scope(**local_vars))
        
    def delete_scope(self, id) -> dict:
        """
        Deletes the specified scope.
        
        **WARNING:** This can not be undone
        :param id: The ID of the scope
        """
        local_vars = locals()
        if 'self' in local_vars: del local_vars['self']

        headers = self.headers.copy()
        r = requests.request(
            "DELETE", 
            "https://fangcloudservices.pythonanywhere.com/api/v1/project/scope?id={}".format(self._url_encode(id)), 
            headers=headers
        )
        
        return self._check_status(r, lambda: self.delete_scope(**local_vars))
        