from datetime import datetime, timedelta

from .. import enums


class ReservationApiMixin:
    @staticmethod
    def reservation_timeslot_adjuster(*, user_time, round_low=False):
        """
        Round a datetime to the nearest half hour, which is required for some times in netlab.

        :param datetime.datetime user_time: Must be a datetime object. Adjusted for fulfilling time slot
            if not already time slot acceptable.
        :param bool round_low: True: rounds time in minutes down to the low timeslot. False: rounds time up to next
            timeslot.

        :return: Datetime rounded to half hour.
        :rtype: datetime.datetime
        """

        if round_low:
            adjusted_minutes = 30 * (user_time.minute // 30)  # floor division used to round minutes down
            adjusted_time = user_time.replace(minute=adjusted_minutes)
        else:
            adjusted_time = user_time + (datetime.min - user_time) % timedelta(minutes=30)
        return adjusted_time.replace(second=0, microsecond=0)

    def reservation_make(self, *, type, pod_id, cls_id=None, end_time, start_time=None, acc_id=None,
                         reserver_id=None, tz_id=None, tz_olson=None, pt_id=None, ex_id=None,
                         init_config=enums.InitConfig.NONE, **kwargs):
        """
        :param ReservationType type: Type of reservation.
        :param int pod_id: Pod identifier.
        :param int cls_id: Class identifier. Not required where **type** is 'INSTRUCTOR'.
        :param datetime.datetime end_time: Local time end of reservation. This must be on a half hour slot boundary.
        :param datetime.datetime start_time: Local time start of reservation. If not specified it will use the current
            datetime.
        :param int tz_id: Timezone identifier.
        :param str tz_olson: IANA timezone name. If not specified, the timezone of the user identified by acc_id will be
            used.
        :param int pt_id: Pod type identifier.
        :param int ex_id: Exercise identifier.
        :param int acc_id: Account identifier for the reservation. Defaults to **reserver_id**.
        :param InitConfig init_config: Initial configuration.
        :param int reserver_id: Reserver identifier. Defaults to the request user. An admin can use a **reserver_id** to
            make reservations for others.

        :return: On success, returns an object with the following properties:
        :rtype: dict

        **Properties**

        res_id
            Reservation identifier.
        res_start_utc
            Reservation start in UTC.
        res_end_utc
            Reservation end in UTC.
        res_minutes
            Reservation time in minutes
        """

        method = "reservation.make"

        # Because for some reason these are diffrent
        if type is enums.ReservationType.INDIVIDUAL:
            type = 'INDIVIDUAL'
        elif type is enums.ReservationType.ILT_CLASS:
            type = 'ILT_CLASS'
        elif type is enums.ReservationType.INSTRUCTOR:
            type = 'INSTRUCTOR'
        elif type is enums.ReservationType.TEAM:
            type = 'TEAM'

        res_param = {
            'type': type,
            'pod_id': pod_id,
            'end_time': end_time,
        }

        if start_time:
            res_param['start_time'] = start_time
        if acc_id:
            res_param['acc_id'] = acc_id
        if reserver_id:
            res_param['reserver_id'] = reserver_id
        if tz_id:
            res_param['tz_id'] = tz_id
        if tz_olson:
            res_param['tz_olson'] = tz_olson
        if pt_id:
            res_param['pt_id'] = pt_id
        if ex_id:
            res_param['ex_id'] = ex_id
        if cls_id:
            res_param['cls_id'] = cls_id
        if init_config:
            res_param['init_config'] = init_config

        res_param.update(kwargs)
        return self.call(method, **res_param)

    def reservation_get(self, *, res_id, **kwargs):
        """
        Get parameters of a reservation.

        :param int res_id: Reservation identifier.
        :return: On success, returns an object with the following properties:
        :rtype: dict

        **Properties**

        acc_com_id
            Community identifier.
        acc_full_name
            Account display name.
        acc_id
            Account identifier.
        cls_div_id
            Community identifier.
        cls_id
            Class identifier.
        cls_name
            Class name.
        ex_id
            Lab exercise identifier.
        ex_name
            Lab exercise name.
        pod_desc
            Pod description.
        pod_id
            Unique pod identifier.
        pod_name
            Pod name.
        pt_desc
            Pod type description.
        pt_id
            Pod type identifier.
        pt_name
            Account email.
        res_acc_id
            Account ID.
        res_active_time
            Active reservation timestamp.
        res_asm_data
            ???
        res_attend_time
            Time reservation was attended.
        res_cls_id
            Class identifier.
        res_com_id
            Community identifier.
        res_config
            Initial configuration.
        res_done
            Reservation is done.
        res_end
            Timestamp scheduled end of reservation.
        res_ex_id
            Reservation lab exercise identifier.
        res_flags
            ???
        res_id
            Reservation identifier.
        res_init_fail
            Count of failures during initialization.
        res_is_active
            Reservation is active.
        res_minutes
            Count of minutes for reservation.
        res_pod_id
            Reservation pod identifier.
        res_post_time
            Timestamp of actual end of reservation.
        res_preload_time
            Timestamp UTC lab will preload.
        res_pt_id
            Reservation pod type identifier.
        res_remaining_dhms
            Days, hours, min, sec remaining if pod is in ACTIVE_LOAD or ACTIVE_INIT state.
        res_remaining_sec
            Seconds remaining if pod is in ACTIVE_LOAD or ACTIVE_LAB state.
        res_start
            Timestamp to start reservation.
        res_team
            Reservation team setting.
        res_type
            Reservation type. :py:class:`netlab.enums.ReservationType`
        res_uuid
            Reservation unique identifier.
        sched_image
            Location of the scheduler image.
        """

        method = "reservation.get"
        res_param = {'res_id': res_id}

        res_param.update(kwargs)
        return self.call(method, **res_param)

    def reservation_post(self, *, res_id, **kwargs):
        """
        Ends a reservation that has already started.

        :param int res_id: Reservation identifier.

        :return: The change succeeded.
        :rtype: 'OK'
        """

        method = "reservation.post"
        return self.call(method, res_id=res_id, **kwargs)

    def reservation_cancel(self, *, res_id, **kwargs):
        """
        Cancel a reservation in NETLAB+ prior to reservation being started.

        NOTE - if you wish to end a reservation that is already started use reservation_post.

        :param int res_id: Reservation unique identifier.

        :return: The change succeeded.
        :rtype: 'OK'
        """

        method = "reservation.cancel"
        return self.call(method, res_id=res_id, **kwargs)

    def reservation_summary(self, **kwargs):
        """
        This method allows you to retrieve a summary of all reservations.

        :return: On success, returns an object with the following properties:
        :rtype: dict

        **Properties**

        active_reservations
            Count of active reservations.
        completed_reservations
            Count of completed reservations.
        future_reservations
            Count of future reservations.
        pods_in_use
            Count of pods in use.
        total_reservations
            Count of total reservations.

        """

        method = "reservation.summary"
        return self.call(method, **kwargs)

    def reservation_query(self, *, scope=enums.ReservationScope.ALL, min_time=None, max_time=None, com_id=None,
                          cls_id=None, pod_id=None, active=None, **kwargs):
        """
        Return a list of reservations filtered on criteria.

        :param ReservationScope scope: Reservation scope.
        :param bool active: Filter active reservations.
        :param int cls_id: Class identifier.
        :param int com_id: Community identifier.
        :param datetime.datetime max_time: Maximum time range.
        :param datetime.datetime min_time: Minimum time range.
        :param int pod_id: Pod identifier.

        :return: On success, returns an object with the following properties:
        :rtype: list[dict]

        **Properties**

        acc_com_id
            Community identifier.
        acc_full_name
            Account display name.
        acc_id
            Account identifier.
        cls_div_id
            Community identifier.
        cls_id
            Class identifier.
        cls_name
            Class name.
        ex_id
            Lab exercise identifier.
        ex_name
            Lab exercise name.
        pod_desc
            Pod description.
        pod_id
            Unique pod identifier.
        pod_name
            Pod name.
        pt_desc
            Pod type description.
        pt_id
            Pod type identifier.
        pt_name
            Account email.
        res_acc_id
            Account identifier.
        res_active_time
            Active reservation timestamp.
        res_attend_time
            Time reservation was attended.
        res_cls_id
            Class identifier.
        res_com_id
            Community identifier.
        res_config
            Initial configuration.
        res_done
            Reservation is done.
        res_end
            Timestamp scheduled end of reservation.
        res_ex_id
            Reservation lab exercise identifier.
        res_flags
            Reservation hex code for pod state.
        res_id
            Reservation identifier.
        res_init_fail
            Count of failures during initialization.
        res_is_active
            Reservation is active.
        res_minutes
            Count of minutes for reservation.
        res_pod_id
            Reservation pod identifier.
        res_post_time
            Timestamp of actual end of reservation.
        res_pt_id
            Reservation pod type identifier.
        res_start
            Timestamp to start reservation.
        res_team
            Reservation team setting.
        res_type
            Type of reservation. :py:class:`netlab.enums.ReservationType`
        res_uuid
            Reservation unique identifier.
        sched_image
            Location of the scheduler image.
        """

        method = "reservation.query"

        res_param = {}
        if active:
            res_param['active'] = active
        if cls_id:
            res_param['cls_id'] = cls_id
        if com_id:
            res_param['com_id'] = com_id
        if max_time:
            res_param['max_time'] = max_time
        if min_time:
            res_param['min_time'] = min_time
        if pod_id:
            res_param['pod_id'] = pod_id
        if scope:
            res_param['scope'] = scope

        res_param.update(kwargs)
        return self.call(method, **res_param)

    def reservation_time_now(self, *, tz_id=None, tz_olson=None, **kwargs):
        """
        This method returns the current NETLAB time.

        :param int tz_id: Timezone identifier.
        :param str tz_olson:

        :return: On success, returns an object with the following properties:
        :rtype: dict

        **Properties**

        local
            Local date time.
        local_am_pm
            AM/PM indicator.
        local_day
            Local day of the month.
        local_day_of_week
            Local day of the week.
        local_hmap
            Local time and am/pm.
        local_hms
            Local time.
        local_hour_12
            Local 12 Hour time.
        local_hour_24
            Local 24 Hour time.
        local_minute
            Local minute time.
        local_second
            Local second time.
        local_year
            Local year.
        local_ymd
            Local YYYY-MM-DD.
        tz_id
            Timezone identifier.
        tz_is_dst
            Timezone is in Daylight Savings Time.
        tz_name
            Name of the timezone.
        tz_olson
            Timezone olson.
        utc
            Coordinated Universal Time.
        """

        method = "reservation.time.now"
        res_param = {}

        if not (tz_id is None or tz_id is 0):  # tz_id==0 is utc, but this fails if passed to api
            res_param['tz_id'] = tz_id
        if tz_olson is not None:
            res_param['tz_olson'] = tz_olson

        res_param.update(kwargs)
        return self.call(method, **res_param)

    def reservation_time_offset(self, *, time=None, op=None, tz_id_in=None,
                                tz_olson_in=None, tz_id_out=None, tz_olson_out=None,
                                slot_roundup=False, years=None, months=None,
                                weeks=None, days=None, hours=None,  minutes=None,
                                seconds=None, **kwargs):
        """
        This method returns the current time with a timezone offset.

        :param datetime time: Time input.
        :param str op: Operator. "+" rounds up. "-" rounds down.
        :param int tz_id_in: Timezone identifier input.
        :param str tz_olson_in: Timezone olson input.
        :param int tz_id_out: Timezone identifier output.
        :param str tz_olson_out: Timezone olson output.
        :param bool slot_roundup: Round up to nearest slot. i.e. 30 min interval. Defaults to False.
        :param int years: Number of years to increase/decrease.
        :param int months: Number of months to increase/decrease.
        :param int weeks: Number of weeks to increase/decrease.
        :param int days: Number of days to increase/decrease.
        :param int hours: Number of hours to increase/decrease.
        :param int minutes: Number of minutes to increase/decrease.
        :param int seconds: Number of seconds to increase/decrease.

        :return: On success, returns an object with the following properties:
        :rtype: dict

        **Properties**

        days
            int
        hours
            int
        minutes
            int
        months
            int
        time_in_local
            datetime
        time_in_utc
            datetime
        time_out_local
            datetime
        time_out_utc
            datetime
        tz_id_in
            int
        tz_id_out
            int
        tz_olson_in
            str
        tz_olson_out
            str
        """

        method = "reservation.time.offset"
        res_param = {'slot_roundup': slot_roundup}

        if time:
            res_param['time'] = time
        if op:
            res_param['op'] = op
        if not (tz_id_in is None or tz_id_in is 0):  # tz_id==0 is utc, but this fails if passed to api
            res_param['tz_id_in'] = tz_id_in
        if tz_olson_in:
            res_param['tz_olson_in'] = tz_olson_in
        if not (tz_id_out is None or tz_id_out is 0):  # tz_id==0 is utc, but this fails if passed to api
            res_param['tz_id_out'] = tz_id_out
        if tz_olson_out:
            res_param['tz_olson_out'] = tz_olson_out
        if years:
            res_param['years'] = years
        if months:
            res_param['months'] = months
        if weeks:
            res_param['weeks'] = weeks
        if days:
            res_param['days'] = days
        if hours:
            res_param['hours'] = hours
        if minutes:
            res_param['minutes'] = minutes
        if seconds:
            res_param['seconds'] = seconds

        res_param.update(kwargs)
        return self.call(method, **res_param)

    def reservation_time_delta(self, *, end, start=None, tz_id=None, tz_olson=None, **kwargs):
        """
        This method returns time between two timestamps with optional timezone offset.

        :param datetime.datetime end: End time. This must be on a half hour increment.
        :param datetime.datetime start: Start time. Defaults to now if not specified.
        :param int tz_id: Timezone identifier.
        :param str tz_olson: Timezone olson.

        :return: On success, returns an object with the following properties:
        :rtype: dict

        **Properties**

        days
            Number of days to increase/decrease.
        hours
            Number of hours to increase/decrease.
        minutes
            Number of minutes to increase/decrease.
        months
            Number of months to increase/decrease.
        """
        method = "reservation.time.delta"

        res_param = {
            'end': end,
        }
        if start:
            res_param['start'] = start
        if not (tz_id is None or tz_id is 0):  # tz_id==0 is utc, but this fails if passed to api
            res_param['tz_id'] = tz_id
        if tz_olson:
            res_param['tz_olson'] = tz_olson

        res_param.update(kwargs)
        return self.call(method, **res_param)
