# coding: utf-8
"""Create and manage users in your Supervisely team."""

# docs
from __future__ import annotations

from collections import namedtuple
from typing import TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Union

from supervisely.api.module_api import ApiField, ModuleApiBase, _get_single_item

if TYPE_CHECKING:
    from pandas.core.frame import DataFrame

from tqdm import tqdm


class UserInfo(NamedTuple):
    """NamedTuple describing a user returned by the API."""

    id: int
    login: str
    role: str
    role_id: int
    name: str
    email: str
    logins: int
    disabled: bool
    last_login: str
    created_at: str
    updated_at: str


class UserApi(ModuleApiBase):
    """API for working with users."""

    def __init__(self, api):
        """
        :param api: :class:`~supervisely.api.api.Api` object to use for API connection.
        :type api: :class:`~supervisely.api.api.Api`

        :Usage Example:

            .. code-block:: python

                import supervisely as sly
                api = sly.Api.from_env()
                users = api.user.get_list()
        """
        super().__init__(api)

    Membership = namedtuple("Membership", ["id", "name", "role_id", "role"])

    @staticmethod
    def info_sequence():
        """
        Sequence of fields that are returned by the API to represent UserInfo.

        :Usage Example:

            .. code-block:: python

                UserInfo(
                    id=8,
                    login="alex",
                    role=None,
                    role_id=None,
                    name=None,
                    email=None,
                    logins=20,
                    disabled=False,
                    last_login="2021-03-24T15:06:26.804Z",
                    created_at="2020-04-17T10:24:09.077Z",
                    updated_at="2021-03-24T15:13:01.148Z",
                )
        """
        return [
            ApiField.ID,
            ApiField.LOGIN,
            ApiField.ROLE,
            ApiField.ROLE_ID,
            ApiField.NAME,
            ApiField.EMAIL,
            ApiField.LOGINS,
            ApiField.DISABLED,
            ApiField.LAST_LOGIN,
            ApiField.CREATED_AT,
            ApiField.UPDATED_AT,
        ]

    @staticmethod
    def info_tuple_name():
        """
        Name of the tuple that represents UserInfo.
        """
        return "UserInfo"

    def _convert_json_info(self, info: dict, skip_missing=True):
        res = super(UserApi, self)._convert_json_info(info, skip_missing=skip_missing)
        return UserInfo(**res._asdict())

    def get_info_by_id(self, id: int) -> UserInfo:
        """
        Get User information by User ID.

        :param id: User ID in Supervisely.
        :type id: int
        :returns: Information about User.
        :rtype: :class:`~supervisely.api.user_api.UserInfo`

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                user_info = api.user.get_info_by_id(8)
                print(user_info)
                # Output: [
                #     8,
                #     "alex",
                #     null,
                #     null,
                #     null,
                #     null,
                #     20,
                #     false,
                #     "2021-03-24T15:06:26.804Z",
                #     "2020-04-17T10:24:09.077Z",
                #     "2021-03-24T15:13:01.148Z"
                # ]
        """
        return self._get_info_by_id(id, "users.info")

    def get_info_by_login(self, login: str) -> UserInfo:
        """
        Get User information by User login.

        :param login: User login in Supervisely.
        :type login: str
        :returns: Information about User.
        :rtype: :class:`~supervisely.api.user_api.UserInfo`

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                user_info = api.user.get_info_by_login('alex')
                print(user_info)
                # Output: [
                #     8,
                #     "alex",
                #     null,
                #     null,
                #     null,
                #     null,
                #     20,
                #     false,
                #     "2021-03-24T15:06:26.804Z",
                #     "2020-04-17T10:24:09.077Z",
                #     "2021-03-24T15:13:01.148Z"
                # ]
        """
        filters = [{"field": ApiField.LOGIN, "operator": "=", "value": login}]
        items = self.get_list(filters)
        return _get_single_item(items)

    def get_member_info_by_login(self, team_id: int, login: str) -> UserInfo:
        """
        Get information about team member by Team ID and User login by login.

        :param team_id: Team ID in Supervisely.
        :type team_id: int
        :param login: User login in Supervisely.
        :type login: str
        :returns: Information about User.
        :rtype: :class:`~supervisely.api.user_api.UserInfo`

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                member_info = api.user.get_member_info_by_login(64, 'alex')
                print(member_info)
                # Output: [
                #     8,
                #     "alex",
                #     "manager",
                #     3,
                #     null,
                #     null,
                #     20,
                #     false,
                #     "2021-03-24T15:06:26.804Z",
                #     "2020-04-17T10:24:09.077Z",
                #     "2021-03-24T15:13:01.148Z"
                # ]
        """
        filters = [{"field": ApiField.LOGIN, "operator": "=", "value": login}]
        team_members = self.get_list_all_pages(
            "members.list",
            {ApiField.TEAM_ID: team_id, ApiField.FILTER: filters},
            convert_json_info_cb=self._api.user._convert_json_info,
        )
        return _get_single_item(team_members)

    def get_member_info_by_id(self, team_id: int, user_id: int) -> UserInfo:
        """
        Get information about team member by Team ID and User ID.

        :param team_id: Team ID in Supervisely.
        :type team_id: int
        :param user_id: User ID in Supervisely.
        :type user_id: int
        :returns: Information about User.
        :rtype: :class:`~supervisely.api.user_api.UserInfo`

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                member_info = api.user.get_member_info_by_id(64, 8)
                print(member_info)
                # Output: [
                #     8,
                #     "alex",
                #     "manager",
                #     3,
                #     null,
                #     null,
                #     20,
                #     false,
                #     "2021-03-24T15:06:26.804Z",
                #     "2020-04-17T10:24:09.077Z",
                #     "2021-03-24T15:13:01.148Z"
                # ]
        """
        filters = [{"field": ApiField.ID, "operator": "=", "value": user_id}]
        team_members = self.get_list_all_pages(
            "members.list",
            {ApiField.TEAM_ID: team_id, ApiField.FILTER: filters},
            convert_json_info_cb=self._api.user._convert_json_info,
        )
        return _get_single_item(team_members)

    def get_list(self, filters: List[Dict[str, str]] = None) -> List[UserInfo]:
        """
        Get list of information about Users.

        :param filters: List of params to sort output Users.
        :type filters: List[dict], optional
        :returns: List of information about Users.
        :rtype: :class:`List[UserInfo]`

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                # Get list of Users with id = 8
                user_info = api.user.get_list(filters=[{'field': 'id', 'operator': '=', 'value': '8'}])
                print(user_info)
                # Output: [
                #     8,
                #     "alex",
                #     "manager",
                #     3,
                #     null,
                #     null,
                #     20,
                #     false,
                #     "2021-03-24T15:06:26.804Z",
                #     "2020-04-17T10:24:09.077Z",
                #     "2021-03-24T15:13:01.148Z"
                # ]
        """
        return self.get_list_all_pages("users.list", {ApiField.FILTER: filters or []})

    def create(
        self,
        login: str,
        password: str,
        is_restricted: Optional[bool] = False,
        name: Optional[str] = "",
        email: Optional[str] = "",
    ) -> UserInfo:
        """
        Creates new User with given login and password.

        :param login: New User login.
        :type login: str
        :param password: New User password.
        :type password: str
        :param is_restricted: If True, new User will have no access to Explore section, won't be able to create or switch Teams, and no personal Team will be created for this User during signup.
        :type is_restricted: bool, optional
        :param name: New User name.
        :type name: str, optional
        :param email: New User email.
        :type email: str, optional
        :returns: Information about new User.
        :rtype: :class:`~supervisely.api.user_api.UserInfo`

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                new_user_info = api.user.create('John', 'qwerty', is_restricted=True, name='John Wick', email='excomunicado@gmail.com')
                print(new_user_info)
                # Output: [
                #     274,
                #     "John",
                #     null,
                #     null,
                #     "John Wick",
                #     "excomunicado@gmail.com",
                #     0,
                #     false,
                #     null,
                #     "2021-03-24T16:20:03.110Z",
                #     "2021-03-24T16:20:03.110Z"
                # ]
        """
        response = self._api.post(
            "users.add",
            {
                ApiField.LOGIN: login,
                ApiField.PASSWORD: password,
                ApiField.IS_RESTRICTED: is_restricted,
                ApiField.NAME: name,
                ApiField.EMAIL: email,
            },
        )
        return self.get_info_by_id(response.json()[ApiField.USER_ID])

    def _set_disabled(self, id, disable):
        """
        Check status of the user with given id
        :param id: int
        :param disable: bool
        """
        self._api.post("users.disable", {ApiField.ID: id, ApiField.DISABLE: disable})

    def disable(self, id: int) -> None:
        """
        Disables User with the given ID.

        :param id: User ID in Supervisely.
        :type id: int
        :returns: None
        :rtype: None

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                user_id = 8
                api.user.disable(user_id)
        """
        self._set_disabled(id, True)

    def enable(self, id: int) -> None:
        """
        Enables User with the given ID.

        :param id: User ID in Supervisely.
        :type id: int
        :returns: None
        :rtype: None

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                user_id = 8
                api.user.enable(user_id)
        """
        self._set_disabled(id, False)

    def get_token(self, login):
        raise NotImplementedError()

    def get_teams(self, id: int) -> List[UserInfo]:
        """
        Get list with information about User Teams.

        :param id: User ID in Supervisely.
        :type id: int
        :returns: List of teams where the user is a member (team id, name, and role).
        :rtype: List[:class:`~supervisely.api.user_api.UserInfo`]

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                teams = api.user.get_teams(8)
                print(teams)
                # Output: [
                #     [
                #         9,
                #         "alex",
                #         1,
                #         "admin"
                #     ],
                #     [
                #         64,
                #         "test",
                #         3,
                #         "manager"
                #     ]
                # ]
        """
        response = self._api.post("users.info", {ApiField.ID: id})
        teams_json = response.json()[ApiField.TEAMS]
        teams = []
        for team in teams_json:
            member = self.Membership(
                id=team[ApiField.ID],
                name=team[ApiField.NAME],
                role_id=team[ApiField.ROLE_ID],
                role=team[ApiField.ROLE],
            )
            teams.append(member)
        return teams

    def add_to_team(self, user_id: int, team_id: int, role_id: int) -> None:
        """
        Invites User to Team with the given role.

        :param user_id: User ID in Supervisely.
        :type user_id: int
        :param team_id: Team ID in Supervisely.
        :type team_id: int
        :param role_id: Role ID in Supervisely.
        :type role_id: int
        :returns: None
        :rtype: None

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                user_id = 8
                team_id = 76
                role_id = 5
                api.user.add_to_team(user_id, team_id, role_id)
        """
        user = self.get_info_by_id(user_id)
        self._api.post(
            "members.add",
            {
                ApiField.LOGIN: user.login,
                ApiField.TEAM_ID: team_id,
                ApiField.ROLE_ID: role_id,
            },
        )

    def remove_from_team(self, user_id: int, team_id: int) -> None:
        """
        Removes User from Team.

        :param user_id: User ID in Supervisely.
        :type user_id: int
        :param team_id: Team ID in Supervisely.
        :type team_id: int
        :returns: None
        :rtype: None

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                user_id = 8
                team_id = 76
                api.user.remove_from_team(user_id, team_id)
        """
        self._api.post("members.remove", {ApiField.ID: user_id, ApiField.TEAM_ID: team_id})

    def update(
        self, id: int, password: Optional[str] = None, name: Optional[str] = None
    ) -> UserInfo:
        """
        Updates User info.

        :param id: User ID in Supervisely.
        :type id: int
        :param password: User password.
        :type password: str
        :param name: User name.
        :type name: str
        :returns: New information about User.
        :rtype: :class:`~supervisely.api.user_api.UserInfo`

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                user_info = api.user.update(8, name='Aleksey')
                print(user_info)
                # Output: [
                #     8,
                #     "alex",
                #     null,
                #     null,
                #     "Aleksey",
                #     null,
                #     21,
                #     false,
                #     "2021-03-25T08:06:03.498Z",
                #     "2020-04-17T10:24:09.077Z",
                #     "2021-03-25T08:37:17.257Z"
                # ]
        """
        data = {}
        if password is not None:
            data[ApiField.PASSWORD] = password
        if name is not None:
            data[ApiField.NAME] = name
        if len(data) == 0:
            return
        data[ApiField.ID] = id

        self._api.post("users.editInfo", data)
        return self.get_info_by_id(id)

    def change_team_role(self, user_id: int, team_id: int, role_id: int) -> None:
        """
        Changes User role in the given Team.

        :param user_id: User ID in Supervisely.
        :type user_id: int
        :param team_id: Team ID in Supervisely.
        :type team_id: int
        :param role_id: Role ID in Supervisely.
        :type role_id: int
        :returns: None
        :rtype: None

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                user_id = 8
                team_id = 64
                new_role_id = 2
                api.user.change_team_role(user_id, team_id, new_role_id)
        """
        self._api.post(
            "members.editInfo",
            {
                ApiField.ID: user_id,
                ApiField.TEAM_ID: team_id,
                ApiField.ROLE_ID: role_id,
            },
        )

    def get_team_members(self, team_id: int) -> List[UserInfo]:
        """
        Get list of information about Team Users.

        :param team_id: Team ID in Supervisely.
        :type team_id: int
        :returns: List of information about Team Users
        :rtype: :class:`List[UserInfo]`

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                team_id = 9
                team_members = api.user.get_team_members(team_id)
        """
        team_members = self.get_list_all_pages(
            "members.list",
            {ApiField.TEAM_ID: team_id, ApiField.FILTER: []},
            convert_json_info_cb=self._api.user._convert_json_info,
        )
        return team_members

    def get_team_role(self, user_id: int, team_id: int) -> UserInfo:
        """
        Get Team role for given User and Team IDs.

        :param user_id: User ID in Supervisely.
        :type user_id: int
        :param team_id: Team ID in Supervisely.
        :type team_id: int
        :returns: Membership information for the specified team (team id, name, and role).
        :rtype: :class:`~supervisely.api.user_api.UserInfo`, optional

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                user_id = 8
                team_id = 9
                team_role = api.user.get_team_role(user_id, team_id)
                print(team_role)
                # Output: TeamMembership(id=9, name="alex", role_id=1, role="admin")
        """
        user_teams = self.get_teams(user_id)
        for member in user_teams:
            if member.id == team_id:
                return member
        return None

    def get_member_activity(
        self, team_id: int, user_id: int, progress_cb: Optional[Union[tqdm, Callable]] = None
    ) -> DataFrame:
        """
        Get User activity data.

        :param team_id: Team ID in Supervisely.
        :type team_id: int
        :param user_id: User ID in Supervisely.
        :type user_id: int
        :param progress_cb: Function to check progress.
        :type progress_cb: tqdm or callable, optional
        :returns: Activity data as `pd.DataFrame <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html>`_
        :rtype: :class:`pd.DataFrame`

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                activity = api.user.get_member_activity(64, 8)
                print(activity)
                # Output:
                #    userId               action                      date  ... jobId   tag tagId
                # 0        8        login_to_team  2021-03-13T08:57:26.832Z  ...  None  None  None
                # 1        8  annotation_duration  2021-03-02T13:16:23.833Z  ...  None  None  None
                # 2        8        login_to_team  2021-03-02T13:15:58.775Z  ...  None  None  None
                # 3        8        login_to_team  2021-02-06T09:47:22.999Z  ...  None  None  None
                # ................................................................................
                # 38       8     create_workspace  2021-01-04T12:25:37.916Z  ...  None  None  None
                # 39       8        login_to_team  2021-01-04T12:24:58.257Z  ...  None  None  None
                # 40       8        login_to_team  2021-01-04T12:23:43.056Z  ...  None  None  None
                # 41       8        login_to_team  2021-01-04T11:53:56.447Z  ...  None  None  None
                # [42 rows x 18 columns]
        """
        import pandas as pd

        activity = self._api.team.get_activity(
            team_id, filter_user_id=user_id, progress_cb=progress_cb
        )
        df = pd.DataFrame(activity)
        return df

    def add_to_team_by_login(self, user_login: str, team_id: int, role_id: int) -> Dict[str, int]:
        """
        Invite User to Team with given role by login.

        :param user_login: User login in Supervisely.
        :type user_login: str
        :param team_id: Team ID in Supervisely.
        :type team_id: int
        :param role_id: Role ID in Supervisely.
        :type role_id: int
        :returns: Information about new User in Team
        :rtype: dict

        :Usage Example:

            .. code-block:: python

                import os
                from dotenv import load_dotenv

                import supervisely as sly

                # Load secrets and create API object from .env file (recommended)
                # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
                if sly.is_development():
                    load_dotenv(os.path.expanduser("~/supervisely.env"))

                api = sly.Api.from_env()

                team_id = 13
                role_id = 2
                new_user_data = api.user.add_to_team_by_login('alex', team_id, role_id)
                print(new_user_data)
                # Output: {"userId": 8}
        """
        response = self._api.post(
            "members.add",
            {
                ApiField.LOGIN: user_login,
                ApiField.TEAM_ID: team_id,
                ApiField.ROLE_ID: role_id,
            },
        )
        return response.json()

    def get_ssh_keys(self):
        """ """
        response = self._api.post("users.ssh-keys", {})
        return response.json()

    def get_my_info(self):
        response = self._api.post("users.me", {})
        return self._convert_json_info(response.json())
