from datetime import date
from typing import Any, Dict, List, Literal, Optional, Union, cast

from .. import enums
from ._client_protocol import ClientProtocol


class ClassApiMixin(ClientProtocol):
    "Methods for working with classes."

    class_default_props = ['cls_id', 'cls_name', 'com_id']
    class_props = ['cls_change_ex', 'cls_def_pw_account', 'cls_def_pw_console', 'cls_def_pw_enable',
                   'cls_email_logs', 'cls_end_date', 'cls_id', 'cls_lab_limit', 'cls_max_slots_per_res',
                   'cls_min_hours_btw_res', 'cls_name', 'cls_no_delete', 'cls_retain_ilt', 'cls_retain_period',
                   'cls_retain_st', 'cls_self_sched', 'cls_start_date', 'cls_team_sched', 'cls_uuid', 'com_id',
                   'enrollment', 'leads']

    roster_default_props = ['acc_id', 'acc_user_id', 'acc_email', 'lead']
    roster_props = ['acc_display_name', 'acc_email', 'acc_full_name', 'acc_id', 'acc_last_login', 'acc_logins',
                    'acc_sort_name', 'acc_time_last_access', 'acc_type', 'acc_user_id', 'cls_id', 'lead', 'ros_team']

    async def class_list(
                self,
                *,
                com_id: Optional[int] = None,
                member: bool = False,
                properties: Union[List[str], Literal['default', 'all']] = "default",
                **kwargs
            ) -> List[Dict[str, Any]]:
        """
        This method allows you to retrieve a list of classes and their properties.

        :param com_id: Community ID.
        :param member: If true, get list of classes the caller is in.
        :param properties: List of properties to retrieve. See Properties section for list of available properties.

        :return: On success, returns an object with the requested properties:

        **Properties**

        cls_change_ex
            Allow student to change exercise.
        cls_def_pw_account
            Class default account password.
        cls_def_pw_console
            Class default console password.
        cls_def_pw_enable
            Class default enable password.
        cls_email_logs
            Email reservation logs for real equipment pods. :py:class:`netlab.enums.ClassEmailLogs`
        cls_end_date
            Class end date.
        cls_id
            Unique class identifier.
        cls_lab_limit
            Lab limit settings. :py:class:`netlab.enums.ClassLabLimit`
        cls_max_slots_per_res
            Maximum number of slots per reservation.
        cls_min_hours_btw_res
            Minimum number of hours between reservations.
        cls_name
            Unique class name per community.
        cls_no_delete
            Prevent from deletion.
        cls_retain_ilt
            Retain instructor led logs.
        cls_retain_period
            Number of days to retain class logs.
        cls_retain_st
            Retain student logs.
        cls_self_sched
            Allow students to self schedule.
        cls_start_date
            Class start date.
        cls_team_sched
            Allow teams to schedule.
        cls_uuid
            Class UUID.
        com_id
            Unique community identifier.
        enrollment
            Count of those enrolled.
        leads
            List of leads and properties sorted by acc_sort_name.
        """
        method = "class.list"
        if properties == 'all':
            properties = self.class_props
        if properties == 'default':
            properties = self.class_default_props
        return await self.call(method, com_id=com_id, member=member, properties=properties, **kwargs)

    async def class_add(
                self,
                *,
                cls_name: str,
                com_id: int,
                cls_change_ex: bool = True,
                cls_def_pw_account: Optional[str] = None,
                cls_def_pw_console: Optional[str] = None,
                cls_def_pw_enable: Optional[str] = None,
                cls_email_logs: enums.ClassEmailLogs = enums.ClassEmailLogs.NO,
                cls_end_date: Optional[date] = None,
                cls_lab_limit: enums.ClassLabLimit = enums.ClassLabLimit.ENFORCE,
                cls_max_slots_per_res: Optional[int] = None,
                cls_min_hours_btw_res: Optional[int] = None,
                cls_no_delete: bool = False,
                cls_retain_ilt: bool = False,
                cls_retain_period: Optional[date] = None,
                cls_retain_st: bool = True,
                cls_self_sched: bool = True,
                cls_start_date: Optional[date] = None,
                cls_team_sched: bool = False,
                **kwargs
            ) -> int:
        """
        This method allows you to add a class to the NETLAB+ system.

        :param cls_name: Class name.
        :param com_id: Community ID.

        :param cls_change_ex: Allow student to change exercise.
        :param cls_def_pw_account: Class default account password.
        :param cls_def_pw_console: Class default console password.
        :param cls_def_pw_enable: Class default enable password.
        :param cls_email_logs: Email reservation logs for real equipment pods.
        :param cls_end_date: Class end date.
        :param cls_lab_limit: Class lab time limit.
        :param cls_max_slots_per_res: Maximum number of slots per reservation.
        :param cls_min_hours_btw_res: Minimum hours between reservations.
        :param cls_no_delete: Prevent from deletion.
        :param cls_retain_ilt: Retain instructor led logs.
        :param cls_retain_period: The number of days to retain logs for.
        :param cls_retain_st: Retain student logs.
        :param cls_self_sched: Allow students to self schedule.
        :param cls_start_date: Class start date.
        :param cls_team_sched: Allow teams to schedule.

        :return: On success, returns the **cls_id** of the newly created class.
        """

        method = "class.add"
        params = {'cls_name': cls_name, 'com_id': com_id, 'cls_change_ex': cls_change_ex,
                  'cls_email_logs': cls_email_logs, 'cls_lab_limit': cls_lab_limit, 'cls_no_delete': cls_no_delete,
                  'cls_retain_ilt': cls_retain_ilt, 'cls_retain_st': cls_retain_st, 'cls_self_sched': cls_self_sched,
                  'cls_team_sched': cls_team_sched}

        if cls_def_pw_account:
            params['cls_def_pw_account'] = cls_def_pw_account
        if cls_def_pw_console:
            params['cls_def_pw_console'] = cls_def_pw_console
        if cls_def_pw_enable:
            params['cls_def_pw_enable'] = cls_def_pw_enable

        if cls_start_date:
            params['cls_start_date'] = cls_start_date
        if cls_end_date:
            params['cls_end_date'] = cls_end_date

        if cls_max_slots_per_res:
            params['cls_max_slots_per_res'] = cls_max_slots_per_res
        if cls_min_hours_btw_res:
            params['cls_min_hours_btw_res'] = cls_min_hours_btw_res

        if cls_retain_period:
            params['cls_retain_period'] = cls_retain_period

        params.update(kwargs)
        return int(await self.call(method, **params))

    async def class_get(
                self,
                *,
                cls_id: int,
                properties: Union[List[str], Literal['all', 'default']] = "default",
                **kwargs
            ) -> Dict[str, Any]:
        """
        This method allows you to retrieve a single class and properties.

        :param int cls_id: Class ID.
        :param properties: List of properties to retrieve. Defaults to "all".
        :return: On success, returns an object with the requested properties:

        **Properties**

        cls_change_ex
            Allow student to change exercise.
        cls_def_pw_account
            Class default account password.
        cls_def_pw_console
            Class default console password.
        cls_def_pw_enable
            Class default enable password.
        cls_email_logs
            Email reservation logs for real equipment pods. :py:class:`netlab.enums.ClassEmailLogs`
        cls_end_date
            Class end date.
        cls_id
            Unique class identifier.
        cls_lab_limit
            Lab limit settings. :py:class:`netlab.enums.ClassLabLimit`
        cls_max_slots_per_res
            Maximum number of slots per reservation.
        cls_min_hours_btw_res
            Minimum number of hours between reservations.
        cls_name
            Unique class name per community.
        cls_no_delete
            Prevent from deletion.
        cls_retain_ilt
            Retain instructor led logs.
        cls_retain_period
            Number of days to retain class logs.
        cls_retain_st
            Retain student logs.
        cls_self_sched
            Allow students to self schedule.
        cls_start_date
            Class start date.
        cls_team_sched
            Allow teams to schedule.
        cls_uuid
            Class UUID.
        com_id
            Unique community identifier.
        enrollment
            Count of those enrolled.
        leads
            List of leads and properties sorted by acc_sort_name.
        """

        method = "class.get"
        if properties == 'all':
            properties = self.class_props
        if properties == 'default':
            properties = self.class_default_props
        return await self.call(method, cls_id=cls_id, properties=properties, **kwargs)

    async def class_update(
                self,
                *,
                cls_id: int,
                cls_change_ex: Optional[bool] = None,
                cls_def_pw_account: Optional[str] = None,
                cls_def_pw_console: Optional[str] = None,
                cls_def_pw_enable: Optional[str] = None,
                cls_email_logs: Optional[enums.ClassEmailLogs] = None,
                cls_end_date: Optional[date] = None,
                cls_lab_limit: Optional[enums.ClassLabLimit] = None,
                cls_max_slots_per_res: Optional[int] = None,
                cls_min_hours_btw_res: Optional[int] = None,
                cls_name: Optional[str] = None,
                cls_no_delete: Optional[bool] = None,
                cls_retain_ilt: Optional[bool] = None,
                cls_retain_period: Optional[int] = None,
                cls_retain_st: Optional[bool] = None,
                cls_self_sched: Optional[bool] = None,
                cls_start_date: Optional[date] = None,
                cls_team_sched: Optional[bool] = None,
                cls_ext_slots_per_res: Optional[Union[int, enums.ClassExtensionSlots]] = None,
                **kwargs
            ) -> Literal["OK"]:
        """
        This method allows you to update a class and its properties.

        :param cls_id: Unique class identifier.
        :param cls_change_ex: Allow student to change exercise.
        :param cls_def_pw_account: Class default account password.
        :param cls_def_pw_console: Class default console password.
        :param cls_def_pw_enable: Class default enable password.
        :param cls_email_logs: Email reservation logs for real equipment pods.
        :param cls_end_date: Class end date.
        :param cls_lab_limit: Class lab time limit.
        :param cls_max_slots_per_res: Maximum number of slots per reservation.
        :param cls_min_hours_btw_res: Minimum hours between reservations.
        :param cls_name: Class name.
        :param cls_no_delete: Prevent from deletion.
        :param cls_retain_ilt: Retain instructor led logs.
        :param cls_retain_period: The number of days to retain logs for.
        :param cls_retain_st: Retain student logs.
        :param cls_self_sched: Allow students to self schedule.
        :param cls_start_date: Class start date.
        :param cls_team_sched: Allow teams to schedule.
        :param cls_ext_slots_per_res: Allow extensions to reservations for students and teams.
            Instructors are not restricted to this limit.
        """

        method = "class.update"
        params: Dict[str, Any] = {'cls_id': cls_id}

        if cls_name:
            params['cls_name'] = cls_name
        if cls_start_date:
            params['cls_start_date'] = cls_start_date
        if cls_end_date:
            params['cls_end_date'] = cls_end_date
        if cls_def_pw_account:
            params['cls_def_pw_account'] = cls_def_pw_account
        if cls_def_pw_console:
            params['cls_def_pw_console'] = cls_def_pw_console
        if cls_def_pw_enable:
            params['cls_def_pw_enable'] = cls_def_pw_enable
        if cls_email_logs:
            params['cls_email_logs'] = cls_email_logs
        if cls_lab_limit:
            params['cls_lab_limit'] = cls_lab_limit
        if cls_no_delete is not None:
            params['cls_no_delete'] = cls_no_delete
        if cls_retain_ilt is not None:
            params['cls_retain_ilt'] = cls_retain_ilt
        if cls_retain_st is not None:
            params['cls_retain_st'] = cls_retain_st
        if cls_retain_period:
            params['cls_retain_period'] = cls_retain_period
        if cls_self_sched is not None:
            params['cls_self_sched'] = cls_self_sched
        if cls_team_sched is not None:
            params['cls_team_sched'] = cls_team_sched
        if cls_change_ex is not None:
            params['cls_change_ex'] = cls_change_ex
        if cls_min_hours_btw_res:
            params['cls_min_hours_btw_res'] = cls_min_hours_btw_res
        if cls_max_slots_per_res:
            params['cls_max_slots_per_res'] = cls_max_slots_per_res
        if cls_ext_slots_per_res is not None:
            params['cls_ext_slots_per_res'] = cls_ext_slots_per_res

        params.update(kwargs)
        return await self.call(method, **params)

    async def class_remove(
                self,
                *,
                cls_id: int,
                delete_students: bool = False,
                **kwargs
            ) -> None:
        """
        This method removes a class from the NETLAB+ system.

        :param cls_id: Local system class.
        :param delete_students: `False` Do not delete student accounts. `True` Delete student accounts
            that are exclusively in this class. Student accounts that are members of multiple classes are not deleted.
        """

        method = "class.remove"
        return await self.call(method, cls_id=cls_id, delete_students=delete_students, **kwargs)

    async def class_roster_add(
                self,
                *,
                acc_id: str,
                cls_id: str,
                lead: bool = False,
                ros_team: Optional[str] = None,
                **kwargs
            ) -> Literal["OK"]:
        """
        This method allows you to add a member to a class roster.

        :param acc_id: Unique account identifier.
        :param cls_id: Unique class identifier.
        :param lead: Add as lead.
        :param ros_team: Assign user to a team. Single character, A-Z.
        """

        method = "class.roster.add"
        params = {'acc_id': acc_id, 'cls_id': cls_id, 'lead': lead}

        if ros_team:
            params['ros_team'] = ros_team

        params.update(kwargs)
        return await self.call(method, **params)

    async def class_roster_list(
                self,
                *,
                cls_id: int,
                leads: bool = False,
                **kwargs
            ):
        """
        This method lists users in the class roster.

        :param cls_id: Local system class.
        :param leads: `None` Return all users in the roster (leads and learners).
            `True` Return lead instructors only. `False` Return learners only.

        :return: On success, returns an object with the requested properties:

        **Properties**

        acc_display_name
            Account name people will see this name in discussions, messages and comments.
        acc_email
            Account email.
        acc_id
            Local system account identifier.
        acc_full_name
            Account full name.
        acc_last_login
            Timestamp of last account login. Possibly `None`.
        acc_logins
            Number of times a user has logged in.
        acc_sort_name
            Account name as it appears in sorted lists.
        acc_time_last_access
            Timestamp of last access. Possibly `None`.
        acc_type
            Account type. :py:class:`netlab.enums.AccountType`
        acc_user_id
            Account user identifier.
        cls_id
            Unique class identifier.
        lead
            If user is a lead instructor.
        ros_team
            Assignment of user to a team (designated by team letter). `None` if user is not assigned to a team.
        """

        method = "class.roster.list"
        return await self.call(method, cls_id=cls_id, leads=leads, **kwargs)

    async def class_roster_remove(
                self,
                *,
                acc_id: int,
                cls_id: str,
                **kwargs
            ) -> None:
        """
        This method removes a user from the class roster.

        :param acc_id: Account identifier of user to be removed.
        :param cls_id: Unique class identifier.
        """

        method = "class.roster.remove"
        return await self.call(method, acc_id=acc_id, cls_id=cls_id, **kwargs)

    async def class_roster_get(
                self,
                *,
                cls_id: int,
                acc_id: Optional[int],
                **kwargs,
            ) -> Dict[str, Any]:
        """
        This method returns class roster information for a single user.

        :param cls_id: Local system class identifier.
        :param acc_id: Returns roster information for the user specified by acount ID acc_id.
            If acc_id is not provided, roster information related to the requester is returned.
        :return: On success, returns an object with the requested properties:

        **Properties**

        acc_display_name
            Account name people will see this name in discussions, messages and comments.
        acc_email
            Account email.
        acc_id
            Local system account identifier.
        acc_full_name
            Account full name.
        acc_last_login
            Timestamp of last account login.
        acc_logins
            Number of times a user has logged in.
        acc_sort_name
            Account name as it appears in sorted lists.
        acc_time_last_access
            Timestamp of last access.
        acc_type
            Account type.
        acc_user_id
            Account user identifier.
        cls_id
            Unique class identifier.
        lead
            User is a lead instructor.
        ros_team
            Team identifier [A-Z] or `None` if user is not assigned to a team.
        """
        method = 'class.roster.get'

        return await self.call(method, acc_id=acc_id, cls_id=cls_id, **kwargs)

    async def class_roster_team_update(
                self,
                *,
                cls_id: int,
                ros_team: Optional[str] = None,
                roster_acc_id: List[int],
                **kwargs,
            ) -> str:
        """
        This method will create and update teams (groups of students) for a given class roster.

        :param cls_id: Local system class identifier.
        :param ros_team: A single uppercase letter [A,Z] as the designation of the team to add
            the student(s) to. If this value is `None`, the student(s) are assigned to no team
            (unassigned).
        :param roster_acc_id: A list of account IDs to assign to the specified team.
        """

        method = 'class.roster.team.update'

        acc_id_csv = ','.join(map(str, roster_acc_id))

        return await self.call(method, cls_id=cls_id, ros_team=ros_team, roster_acc_id=acc_id_csv, **kwargs)

    async def class_content_add(
        self,
        *,
        cls_id: int,
        con_id: str,
        **kwargs,
    ) -> None:
        """
        This method allows you to add content to a class.

        :param cls_id: Class identifier.
        :param con_id: Content identifier.
        """

        method = "class.content.add"
        return await self.call(
            method,
            cls_id=cls_id,
            con_id=con_id,
            **kwargs,
        )

    async def class_content_availability(
        self,
        *,
        cls_id: int,
        **kwargs,
    ) -> List[Dict[str, Any]]:
        """
        This method provides a list of available class content.

        :param cls_id: Unnique class identifier.

        :return: On success, returns a list of objects with the requested properties:

        **Properties**

        con_access
            Content is Globally accessible or Private.
        con_aid
            Content author ID.
        con_author
            Content author name.
        con_build
            Build number.
        con_copyright
            Content copyright.
        con_desc
            Content description.
        con_gid
            Unique content identifier.
        con_global
            Is content globally accessible or not.
        con_id
            Unique content identifier.
        con_managed
            Is the content managed or not.
        con_name
            Name of content.
        con_notes
            Content notes.
        con_org
            Content organization.
        con_origin
            Source of the content.
        con_pt_ids
            Comma separated list of required Pod Types.
        con_url
            Content URL.
        """
        method = "class.content.availability"

        return await self.call(
            method,
            cls_id=cls_id,
            **kwargs,
        )

    async def class_content_list(
        self,
        *,
        cls_id: int,
        properties: Union[Literal['all'], List[str]] = 'all',
        **kwargs,
    ) -> List[Dict[str, Any]]:
        """
        This method provides a list of content associated with a class.

        :param cls_id: Unique class identifier.
        :param properties: Array of properties to retrieve.

        :return: On success, returns a list of objects with the requested properties:

        **Properties**

        con_access
            Content is Globally accessible or Private.
        con_aid
            Content author ID.
        con_author
            Content author name.
        con_build
            Build number.
        con_copyright
            Content copyright.
        con_desc
            Content description.
        con_gid
            Unique content identifier.
        con_global
            Is content globally accessible or not.
        con_id
            Unique content identifier.
        con_managed
            Is the content managed or not.
        con_name
            Name of content.
        con_notes
            Content notes.
        con_org
            Content organization.
        con_origin
            Source of the content.
        con_pt_ids
            Comma separated list of required Pod Types.
        con_url
            Content URL.
        """
        method = "class.content.list"

        if properties == 'all':
            properties = cast(Literal['all'], None)

        return await self.call(
            method,
            cls_id=cls_id,
            properties=properties,
            **kwargs,
        )

    async def class_content_remove(
        self,
        *,
        cls_id: int,
        con_id: str,
        **kwargs,
    ) -> None:
        """
        This method removes content from a class.

        :param cls_id: Class identifier.
        :param con_id: Content identifier.
        """
        method = "class.content.remove"

        return await self.call(
            method,
            cls_id=cls_id,
            con_id=con_id,
            **kwargs,
        )
