"""
Workspaces

Workspaces represent where people work, such as conference rooms, meeting spaces, lobbies, and lunch rooms. Devices may
be associated with workspaces.

Viewing the list of workspaces in an organization requires an administrator auth token with
the spark-admin:workspaces_read scope. Adding, updating, or deleting workspaces in an organization requires an
administrator auth token with the spark-admin:workspaces_write scope.

The Workspaces API can also be used by partner administrators acting as administrators of a different organization
than their own. In those cases an orgId value must be supplied, as indicated in the reference documentation for the
relevant endpoints.
"""
import datetime
from collections.abc import Generator
from typing import Optional

from pydantic import Field

from ..api_child import ApiChild
from ..base import ApiModel, enum_str
from ..base import SafeEnum as Enum

__all__ = ['WorkSpaceType', 'CallingType', 'CalendarType', 'WorkspaceEmail', 'Calendar', 'WorkspaceSupportedDevices',
           'WorkspaceCallingHybridCalling', 'WorkspaceWebexCalling', 'WorkspaceCalling', 'DeviceHostedMeetings',
           'HotdeskingStatus', 'WorkspaceIndoorNavigation', 'WorkspaceHealthLevel', 'WorkspaceHealthIssue',
           'WorkspaceHealth', 'Workspace', 'SupportAndConfiguredInfo', 'CapabilityMap', 'WorkspacesApi']

from ..common import DevicePlatform, MaintenanceMode
from ..devices import Device


class WorkSpaceType(str, Enum):
    """
    workspace type
    """
    #: No workspace type set.
    notSet = 'notSet'
    #: High concentration.
    focus = 'focus'
    #: Brainstorm/collaboration.
    huddle = 'huddle'
    #: Dedicated meeting space.
    meetingRoom = 'meetingRoom'
    #: Dedicated meeting space.
    open = 'open'
    #: Individual.
    desk = 'desk'
    #: Unspecified.
    other = 'other'


class CallingType(str, Enum):
    """
    Calling types: freeCalling, webexEdgeForDevices, thirdPartySipCalling, webexCalling and none.
    """
    #: Free Calling.
    free = 'freeCalling'
    #: Webex Edge For Devices.
    edge_for_devices = 'webexEdgeForDevices'
    #: thirdPartySipCalling
    third_party = 'thirdPartySipCalling'
    #: Webex Calling.
    webex = 'webexCalling'
    none = 'none'


class CalendarType(str, Enum):
    """
    type of calendar integration
    """
    #: No calendar.
    none = 'none'
    #: Google Calendar.
    google = 'google'
    #: Microsoft Exchange or Office 365.
    microsoft = 'microsoft'


class WorkspaceEmail(ApiModel):
    email_address: Optional[str] = None


class Calendar(ApiModel):
    #: Calendar type. Calendar of type none does not include an emailAddress field.
    calendar_type: Optional[CalendarType] = Field(alias='type', default=None)
    #: Workspace email address. Will not be set when the calendar type is `none`.
    email_address: Optional[str] = None
    #: The `ResourceGroup` ID is associated with the workspace and is only applicable for on-premise MICROSOFT
    #: calendar.
    resource_group_id: Optional[str] = None


class WorkspaceSupportedDevices(str, Enum):
    #: Workspace supports collaboration devices.
    collaboration_devices = 'collaborationDevices'
    #: Workspace supports MPP phones.
    phones = 'phones'


class WorkspaceCallingHybridCalling(ApiModel):
    #: End user email address in Cisco Unified CM.
    email_address: Optional[str] = None


class WorkspaceWebexCalling(ApiModel):
    #: End user phone number in Cisco Unified CM.
    phone_number: Optional[str] = None
    #: End user extension in Cisco Unified CM.
    extension: Optional[str] = None
    #: Calling location ID.
    location_id: Optional[str] = None
    #: The Webex Calling license associated with this workspace.
    licenses: Optional[list[str]] = None


class WorkspaceCalling(ApiModel):
    type: Optional[CallingType] = None
    #: The `hybridCalling` object only applies when calling type is `hybridCalling`.
    hybrid_calling: Optional[WorkspaceCallingHybridCalling] = None
    #: The webexCalling object only applies when calling type is webexCalling.
    #: due to a backend limitation this information is never returned by the workspace API and only has to be used when
    #: creating a workspace
    webex_calling: Optional[WorkspaceWebexCalling] = None


class DeviceHostedMeetings(ApiModel):
    enabled: bool


class HotdeskingStatus(str, Enum):
    #: Workspace supports hotdesking.
    on = 'on'
    #: Workspace does not support hotdesking.
    off = 'off'
    none_ = 'none'


class WorkspaceIndoorNavigation(ApiModel):
    #: URL of a map locating the workspace.
    url: Optional[str] = None


class WorkspaceHealthLevel(str, Enum):
    error = 'error'
    warning = 'warning'
    info = 'info'
    ok = 'ok'


class WorkspaceHealthIssue(ApiModel):
    #: Issue id.
    id: Optional[str] = None
    #: Issue created timestamp.
    created_at: Optional[datetime.datetime] = None
    #: Issue title.
    title: Optional[str] = None
    #: Issue description.
    description: Optional[str] = None
    #: Recommended action to mitigate issue.
    recommended_action: Optional[str] = None
    #: Issue level.
    level: Optional[str] = None


class WorkspaceHealth(ApiModel):
    #: Health level. The level is based on the list of issues associated with the workspace.
    level: Optional[WorkspaceHealthLevel] = None
    #: A list of workspace issues.
    issues: Optional[list[WorkspaceHealthIssue]] = None


class WorkspacePlannedMaintenance(ApiModel):
    #: The planned maintenance mode for the workspace
    mode: Optional[MaintenanceMode] = None
    #: The start of the planned maintenance period.
    start_time: Optional[datetime.datetime] = None
    #: The end of the planned maintenance period.
    end_time: Optional[datetime.datetime] = None


class SupportAndConfiguredInfo(ApiModel):
    #: Is the workspace capability supported or not.
    supported: Optional[bool] = None
    #: Is the workspace capability configured or not.
    configured: Optional[bool] = None


class CapabilityMap(ApiModel):
    #: Occupancy detection.
    occupancy_detection: Optional[SupportAndConfiguredInfo] = None
    #: Presence detection.
    presence_detection: Optional[SupportAndConfiguredInfo] = None
    #: Ambient noise.
    ambient_noise: Optional[SupportAndConfiguredInfo] = None
    #: Sound level.
    sound_level: Optional[SupportAndConfiguredInfo] = None
    #: Temperature.
    temperature: Optional[SupportAndConfiguredInfo] = None
    #: Air quality.
    air_quality: Optional[SupportAndConfiguredInfo] = None
    #: Relative humidity.
    relative_humidity: Optional[SupportAndConfiguredInfo] = None
    hot_desking: Optional[SupportAndConfiguredInfo] = None
    check_in: Optional[SupportAndConfiguredInfo] = None
    adhoc_booking: Optional[SupportAndConfiguredInfo] = None


class Workspace(ApiModel):
    """
    Workspace details
    """
    #: Unique identifier for the Workspace.
    workspace_id: Optional[str] = Field(alias='id', default=None)
    #: OrgId associate with the workspace.
    org_id: Optional[str] = None
    #: Location associated with the workspace (ID to use for the `/locations API
    #: <https://developer.webex.com/docs/api/v1/locations>`_).
    location_id: Optional[str] = None
    #: Legacy workspace location ID associated with the workspace. Prefer `locationId`.
    workspace_location_id: Optional[str] = None
    #: Floor associated with the workspace.
    floor_id: Optional[str] = None
    #: A friendly name for the workspace.
    display_name: Optional[str] = None
    #: How many people the workspace is suitable for.
    capacity: Optional[int] = None
    #: The workspace type.
    workspace_type: Optional[WorkSpaceType] = Field(alias='type', default=None)
    #: SipUrl to call all the devices associated with the workspace.
    sip_address: Optional[str] = None
    #: The date and time that the workspace was registered
    created: Optional[datetime.datetime] = None
    #: Calling type.
    calling: Optional[WorkspaceCalling] = None
    #: The hybridCalling object only applies when calling type is hybridCalling.
    hybrid_calling: Optional[WorkspaceEmail] = None
    #: Calendar type. Calendar of type none does not include an emailAddress field.
    calendar: Optional[Calendar] = None
    #: Notes associated to the workspace.
    notes: Optional[str] = None
    #: Hot desking status of the workspace.
    hotdesking_status: Optional[HotdeskingStatus] = None
    #: The supported devices for the workspace.
    supported_devices: Optional[WorkspaceSupportedDevices] = None
    #: Device hosted meetings configuration.
    device_hosted_meetings: Optional[DeviceHostedMeetings] = None
    #: The device platform.
    device_platform: Optional[DevicePlatform] = None
    #: Indoor navigation configuration.
    indoor_navigation: Optional[WorkspaceIndoorNavigation] = None
    #: The health of the workspace.
    health: Optional[WorkspaceHealth] = None
    #: A list of devices associated with the workspace.
    devices: Optional[list[Device]] = None
    #: The map of workspace capabilities.
    capabilities: Optional[CapabilityMap] = None
    #: The planned maintenance for the workspace.
    planned_maintenance: Optional[WorkspacePlannedMaintenance] = None

    def update_or_create(self, for_update: bool = False) -> dict:
        """
        JSON for update or create

        :meta private:
        """
        # supported device cannot be changed later
        return self.model_dump(mode='json',
                               exclude_unset=True,
                               exclude_none=True,
                               by_alias=True,
                               exclude={'workspace_id': True,
                                        'org_id': True,
                                        'sip_address': True,
                                        'created': True,
                                        'hybrid_calling': True,
                                        'health': True,
                                        'devices': True,
                                        # only include workspace_location_id if no location_id is given
                                        # location_id is the preferred/new way of setting the location
                                        'workspace_location_id': not (self.workspace_location_id and
                                                                      not self.location_id),
                                        'supported_devices': for_update,
                                        'planned_maintenance': True
                                        })

    @staticmethod
    def create(*, display_name: str) -> 'Workspace':
        """
        minimal settings for a :meth:`WorkspacesApi.create` call

        :return: :class:`Workspace`
        """
        return Workspace(display_name=display_name)


class WorkspacesApi(ApiChild, base='workspaces'):
    """
    Workspaces API

    Workspaces represent where people work, such as conference rooms, meeting spaces, lobbies, and lunch rooms. Devices
    may be associated with workspaces.
    Viewing the list of workspaces in an organization requires an administrator auth token with the
    spark-admin:workspaces_read scope. Adding, updating, or deleting workspaces in an organization requires an
    administrator auth token with the spark-admin:workspaces_write scope.
    The Workspaces API can also be used by partner administrators acting as administrators of a different organization
    than their own. In those cases an orgId value must be supplied, as indicated in the reference documentation for the
    relevant endpoints.
    """

    def list(self, location_id: str = None, workspace_location_id: str = None, floor_id: str = None,
             display_name: str = None, capacity: int = None, workspace_type: WorkSpaceType = None,
             calling: CallingType = None, supported_devices: WorkspaceSupportedDevices = None,
             calendar: CalendarType = None, device_hosted_meetings_enabled: bool = None,
             device_platform: DevicePlatform = None, health_level: WorkspaceHealthLevel = None,
             include_devices: bool = None, include_capabilities: bool = None,
             planned_maintenance: MaintenanceMode = None, custom_attribute: str = None, org_id: str = None,
             **params) -> Generator[Workspace, None, None]:
        """
        List Workspaces

        Use query parameters to filter the response. The `orgId` parameter can only be used by admin users of another
        organization (such as partners). The `locationId`, `workspaceLocationId`, `indoorNavigation`, `floorId`,
        `capacity` and `type` fields will only be present for workspaces that have a value set for them. The special
        values `notSet` (for filtering on category) and `-1` (for filtering on capacity) can be used to filter for
        workspaces without a type and/or capacity.

        :param location_id: Location associated with the workspace. Values must originate from the /locations API and
            not the legacy /workspaceLocations API.
        :type location_id: str
        :param workspace_location_id: Location associated with the workspace. Both values from the /locations API and
            the legacy /workspaceLocations API are supported. This field is deprecated and integrations should prefer
            `locationId` going forward.
        :type workspace_location_id: str
        :param floor_id: Floor associated with the workspace.
        :type floor_id: str
        :param display_name: List workspaces by display name.
        :type display_name: str
        :param capacity: List workspaces with the given capacity. Must be -1 or higher. A value of -1 lists workspaces
            with no capacity set.
        :type capacity: int
        :param workspace_type: List workspaces by type. Possible values: notSet, focus, huddle, meetingRoom, open,
            desk, other
        :type workspace_type: :class:`WorkSpaceType`
        :param calling: List workspaces by calling type. Possible values: freeCalling, hybridCalling, webexCalling,
            webexEdgeForDevices, thirdPartySipCalling, none
        :type calling: :class:`CallingType`
        :param supported_devices: List workspaces by supported devices. Possible values: collaborationDevices, phones
        :type supported_devices: str
        :param calendar: List workspaces by calendar type. Possible values: none, google, microsoft
        :type calendar: :class:`CalendarType`
        :param device_hosted_meetings_enabled: List workspaces enabled for device hosted meetings.
        :type device_hosted_meetings_enabled: bool
        :param device_platform: List workspaces by device platform.
        :type device_platform: DevicePlatform
        :param health_level: List workspaces by health level.
        :type health_level: WorkspaceHealthLevel
        :param include_devices: Flag identifying whether to include the devices associated with the workspace in the
            response.
        :type include_devices: bool
        :param include_capabilities: Flag identifying whether to include the workspace capabilities in the response.
        :type include_capabilities: bool
        :param planned_maintenance: List workspaces with given maintenance mode.
        :type planned_maintenance: WorkspacePlannedMaintenanceMode
        :param custom_attribute: List workspaces with given custom attribute key.
        :type custom_attribute: str
        :param org_id: List workspaces in this organization. Only admin users of another organization
            (such as partners) may use this parameter.
        :type org_id: str
        :return: generator of :class:`Workspace` instances
        """
        if org_id is not None:
            params['orgId'] = org_id
        if location_id is not None:
            params['locationId'] = location_id
        if workspace_location_id is not None:
            params['workspaceLocationId'] = workspace_location_id
        if floor_id is not None:
            params['floorId'] = floor_id
        if display_name is not None:
            params['displayName'] = display_name
        if capacity is not None:
            params['capacity'] = capacity
        if workspace_type is not None:
            params['type'] = enum_str(workspace_type)
        if calling is not None:
            params['calling'] = calling
        if supported_devices is not None:
            params['supportedDevices'] = enum_str(supported_devices)
        if calendar is not None:
            params['calendar'] = calendar
        if device_hosted_meetings_enabled is not None:
            params['deviceHostedMeetingsEnabled'] = str(device_hosted_meetings_enabled).lower()
        if device_platform is not None:
            params['devicePlatform'] = enum_str(device_platform)
        if health_level is not None:
            params['healthLevel'] = enum_str(health_level)
        if include_devices is not None:
            params['includeDevices'] = str(include_devices).lower()
        if include_capabilities is not None:
            params['includeCapabilities'] = str(include_capabilities).lower()
        if planned_maintenance is not None:
            params['plannedMaintenance'] = enum_str(planned_maintenance)
        if custom_attribute is not None:
            params['customAttribute'] = custom_attribute
        ep = self.ep()
        # noinspection PyTypeChecker
        return self.session.follow_pagination(url=ep, model=Workspace, params=params)

    def create(self, settings: Workspace, org_id: str = None):
        """
        Create a Workspace

        The `locationId`, `workspaceLocationId`, `floorId`, `indoorNavigation`, `capacity`, `type`, `notes` and
        `hotdeskingStatus` parameters are optional, and omitting them will result in the creation of a workspace
        without these values set, or set to their default. A `locationId` must be provided when the `floorId` is set.
        Calendar and calling can also be set for a new workspace. Omitting them will default to free calling and no
        calendaring. The `orgId` parameter can only be used by admin users of another organization (such as partners).

        * Information for Webex Calling fields may be found here: `locations
        <https://developer.webex.com/docs/api/v1/locations/list-locations>`_, `available numbers

        * The `locationId` and `supportedDevices` fields cannot be changed once configured.

        * When creating a `webexCalling` workspace that is not hot desk only, a `locationId` and either a `phoneNumber`
          or `extension` or both is required. Furthermore, it is possible to set the `licenses` field with a list of
          Webex Calling license IDs, if desired. If multiple license IDs are provided, the oldest suitable one will be
          applied. If no licenses are supplied, the oldest suitable one from the active subscriptions will be
          automaticaly applied.

        * When creating a hot desk only workspace, `phoneNumber` and `extension` fields are not applicable.
          Furthermore, `deviceHostedMeetingsEnabled`, and `calendar` services are not applicable. If any of these fields
           are provided the API will return an error. The `calling` type is `webexCalling`.

        :param settings: settings for new Workspace
        :type settings: :class:`Workspace`
        :param org_id: OrgId associated with the workspace. Only admin users of another organization
            (such as partners) may use this parameter.
        :type org_id: str
        :return: new workspace
        :rtype: :class:`Workspace`
        """
        if org_id:
            settings.org_id = org_id
        data = settings.update_or_create()
        url = self.ep()
        data = self.post(url, json=data)
        return Workspace.model_validate(data)

    def details(self, workspace_id, include_devices: bool = None) -> Workspace:
        """
        Get Workspace Details

        Shows details for a workspace, by ID. The `locationId`, `workspaceLocationId`, `floorId`, `indoorNavigation`,
        `capacity`, `type` and `notes` fields will only be present if they have been set for the workspace.

        :param workspace_id: A unique identifier for the workspace.
        :type workspace_id: str
        :param include_devices: Flag identifying whether to include the devices associated with the workspace in the
            response.
        :type include_devices: bool
        :return: workspace details
        :rtype: :class:`Workspace`
        """
        params = {}
        if include_devices is not None:
            params['includeDevices'] = str(include_devices).lower()
        url = self.ep(workspace_id)
        return Workspace.model_validate(self.get(url, params=params))

    def update(self, workspace_id, settings: Workspace) -> Workspace:
        """
        Update a Workspace

        Updates details for a workspace by ID.

        Specify the workspace ID in the `workspaceId` parameter in the URI. Include all details for the workspace that
        are present in a `GET request for the workspace details
        <https://developer.webex.com/docs/api/v1/workspaces/get-workspace-details>`_. Not including the optional
        `capacity`, `type` or
        `notes` fields will result in the fields no longer being defined for the workspace. A `locationId` must be
        provided when the `floorId` is set. The `locationId`, `workspaceLocationId`, `floorId`, `supportedDevices`,
        `calendar` and `calling` fields do not change when omitted from the update request.

        * Information for Webex Calling fields may be found here: `locations
        <https://developer.webex.com/docs/api/v1/locations/list-locations>`_ and `available numbers

        * Updating the `calling` parameter is only supported if the existing `calling` type is `freeCalling`, `none`,
          `thirdPartySipCalling` or `webexCalling`.

        * Updating the `calling` parameter to `none`, `thirdPartySipCalling` or `webexCalling` is not supported if the
          workspace contains any devices.

        * The `locationId` and `supportedDevices` fields cannot be changed once configured.

        * When updating `webexCalling` information on a workspace that is not hot desk only, a `locationId` and either
          a `phoneNumber` or `extension` or both is required. Furthermore, the `licenses` field can be set with a list
          of Webex Calling license IDs, if desired. If multiple license IDs are provided, the oldest suitable one will
          be applied. If a previously applied license ID is omitted, it will be replaced with one from the list
          provided. If the `licenses` field is omitted, the current calling license will be retained.

        * When specifying a hot desk only license on a hot desk only workspace, `deviceHostedMeetingsEnabled`, and
          `calendar` services are not supported and will be automatically disabled. In addition to this, the
          `phoneNumber` and `extension` will be removed from the workspace. Attempting to enable any of these services,
          or provide a `phoneNumber` or `extension` will result in an error. The `calling` type for these requests is
          `webexCalling`.

        :param workspace_id: A unique identifier for the workspace.
        :type workspace_id: str
        :param settings: new workspace settings
        :type settings: :class:`Workspace`
        :return: updated workspace
        :rtype: :class:`Workspace`
        """
        url = self.ep(workspace_id)
        j_data = settings.update_or_create(for_update=True)
        data = self.put(url, json=j_data)
        return Workspace.model_validate(data)

    def delete_workspace(self, workspace_id):
        """
        Delete a Workspace

        Deletes a workspace, by ID. Will also delete all devices associated with the workspace. Any deleted devices
        will need to be reactivated.

        :param workspace_id: A unique identifier for the workspace.
        :type workspace_id: str
        """
        url = self.ep(workspace_id)
        self.delete(url)

    def capabilities(self, workspace_id: str) -> CapabilityMap:
        """
        Shows the capabilities for a workspace by ID.
        Returns a set of capabilities, including whether or not the capability is supported by any device in the
        workspace, and if the capability is configured (enabled). For example for a specific capability like
        occupancyDetection, the API will return if the capability is supported and/or configured such that occupancy
        detection data will flow from the workspace (device) to the cloud. Specify the workspace ID in the workspaceId
        parameter in the URI.

        :param workspace_id: A unique identifier for the workspace.
        :type workspace_id: str

        """
        url = self.ep(f'{workspace_id}/capabilities')
        data = super().get(url=url)
        return CapabilityMap.model_validate(data["capabilities"])
