"""Define the SmartThings Cloud API."""

from typing import (
    List,
    Optional,
    Sequence,
)

from aiohttp import ClientSession

from .api import Api
from .app import (
    App,
    AppEntity,
    AppOAuth,
    AppOAuthClient,
    AppOAuthClientEntity,
    AppOAuthEntity,
    AppSettings,
    AppSettingsEntity,
)
from .device import DeviceEntity
from .installedapp import (
    InstalledAppEntity,
    InstalledAppStatus,
)
from .location import LocationEntity
from .oauthtoken import OAuthToken
from .room import (
    Room,
    RoomEntity,
)
from .scene import SceneEntity
from .subscription import (
    Subscription,
    SubscriptionEntity,
)


class SmartThings:
    """Define a class for interacting with the SmartThings Cloud API."""

    __slots__ = ["_service"]

    def __init__(self, session: ClientSession, token: str):
        """Initialize the SmartThingsApi."""
        self._service = Api(session, token)

    async def locations(self) -> List[LocationEntity]:
        """Retrieve SmartThings locations."""
        resp = await self._service.get_locations()
        return [LocationEntity(self._service, entity) for entity in resp]

    async def location(self, location_id: str) -> LocationEntity:
        """Retrieve a location with the specified ID."""
        entity = await self._service.get_location(location_id)
        return LocationEntity(self._service, entity)

    async def rooms(self, location_id: str) -> List[RoomEntity]:
        """Retrieve a list of rooms for a location."""
        resp = await self._service.get_rooms(location_id)
        return [RoomEntity(self._service, entity) for entity in resp]

    async def room(self, location_id: str, room_id: str) -> RoomEntity:
        """Retrieve a specific room."""
        entity = await self._service.get_room(location_id, room_id)
        return RoomEntity(self._service, entity)

    async def create_room(self, room: Room) -> RoomEntity:
        """Create a room."""
        entity = await self._service.create_room(room.location_id, room.to_data())
        return RoomEntity(self._service, entity)

    async def update_room(self, room: Room) -> RoomEntity:
        """Update a room."""
        entity = await self._service.update_room(
            room.location_id, room.room_id, room.to_data()
        )
        return RoomEntity(self._service, entity)

    async def delete_room(self, location_id: str, room_id: str):
        """Delete a room."""
        return await self._service.delete_room(location_id, room_id) == {}

    async def devices(
        self,
        *,
        location_ids: Optional[Sequence[str]] = None,
        capabilities: Optional[Sequence[str]] = None,
        device_ids: Optional[Sequence[str]] = None
    ) -> List:
        """Retrieve SmartThings devices."""
        params = []
        if location_ids:
            params.extend([("locationId", lid) for lid in location_ids])
        if capabilities:
            params.extend([("capability", cap) for cap in capabilities])
        if device_ids:
            params.extend([("deviceId", did) for did in device_ids])
        resp = await self._service.get_devices(params)
        return [DeviceEntity(self._service, entity) for entity in resp]

    async def device(self, device_id: str) -> DeviceEntity:
        """Retrieve a device with the specified ID."""
        entity = await self._service.get_device(device_id)
        return DeviceEntity(self._service, entity)

    async def apps(self, *, app_type: Optional[str] = None) -> List[AppEntity]:
        """Retrieve list of apps."""
        params = []
        if app_type:
            params.append(("appType", app_type))
        resp = await self._service.get_apps(params)
        return [AppEntity(self._service, entity) for entity in resp]

    async def app(self, app_id: str) -> AppEntity:
        """Retrieve an app with the specified ID."""
        entity = await self._service.get_app(app_id)
        return AppEntity(self._service, entity)

    async def create_app(self, app: App) -> (AppEntity, AppOAuthClient):
        """Create a new app."""
        entity = await self._service.create_app(app.to_data())
        return AppEntity(self._service, entity["app"]), AppOAuthClient(entity)

    async def delete_app(self, app_id: str):
        """Delete an app."""
        return await self._service.delete_app(app_id) == {}

    async def app_settings(self, app_id: str) -> AppSettingsEntity:
        """Get an app's settings."""
        settings = await self._service.get_app_settings(app_id)
        return AppSettingsEntity(self._service, app_id, settings)

    async def update_app_settings(self, data: AppSettings) -> AppSettingsEntity:
        """Update an app's settings."""
        entity = await self._service.update_app_settings(data.app_id, data.to_data())
        return AppSettingsEntity(self._service, data.app_id, entity)

    async def app_oauth(self, app_id: str) -> AppOAuthEntity:
        """Get an app's OAuth settings."""
        oauth = await self._service.get_app_oauth(app_id)
        return AppOAuthEntity(self._service, app_id, oauth)

    async def update_app_oauth(self, data: AppOAuth) -> AppOAuthEntity:
        """Update an app's OAuth settings without having to retrieve it."""
        entity = await self._service.update_app_oauth(data.app_id, data.to_data())
        return AppOAuthEntity(self._service, data.app_id, entity)

    async def generate_app_oauth(self, data: AppOAuth) -> AppOAuthClientEntity:
        """Generate a new oauth client id and secret."""
        entity = await self._service.generate_app_oauth(data.app_id, data.to_data())
        return AppOAuthClientEntity(self._service, data.app_id, entity)

    async def installed_apps(
        self,
        *,
        location_id: Optional[str] = None,
        installed_app_status: Optional[InstalledAppStatus] = None
    ) -> List[InstalledAppEntity]:
        """Get a list of the installed applications."""
        params = []
        if location_id:
            params.append(("locationId", location_id))
        if installed_app_status:
            params.append(("installedAppStatus", installed_app_status.value))
        resp = await self._service.get_installed_apps(params)
        return [InstalledAppEntity(self._service, entity) for entity in resp]

    async def installed_app(self, installed_app_id: str) -> InstalledAppEntity:
        """Get an installedapp with the specified ID."""
        entity = await self._service.get_installed_app(installed_app_id)
        return InstalledAppEntity(self._service, entity)

    async def delete_installed_app(self, installed_app_id: str):
        """Delete an installedapp."""
        result = await self._service.delete_installed_app(installed_app_id)
        return result == {"count": 1}

    async def subscriptions(self, installed_app_id: str) -> List[SubscriptionEntity]:
        """Get an installedapp's subscriptions."""
        resp = await self._service.get_subscriptions(installed_app_id)
        return [SubscriptionEntity(self._service, entity) for entity in resp]

    async def delete_subscriptions(self, installed_app_id: str) -> int:
        """Delete an installedapp's subscriptions."""
        resp = await self._service.delete_all_subscriptions(installed_app_id)
        return resp["count"]

    async def delete_subscription(self, installed_app_id: str, subscription_id: str):
        """Delete an individual subscription."""
        return await self._service.delete_subscription(
            installed_app_id, subscription_id
        ) == {"count": 1}

    async def create_subscription(
        self, subscription: Subscription
    ) -> SubscriptionEntity:
        """Create a new subscription for an installedapp."""
        entity = await self._service.create_subscription(
            subscription.installed_app_id, subscription.to_data()
        )
        return SubscriptionEntity(self._service, entity)

    async def scenes(self, *, location_id: Optional[str] = None):
        """Get a list of scenes and optionally filter by location."""
        params = []
        if location_id:
            params.append(("locationId", location_id))
        resp = await self._service.get_scenes(params)
        return [SceneEntity(self._service, entity) for entity in resp]

    async def execute_scene(self, scene_id: str) -> bool:
        """Execute the scene with the specified id."""
        result = await self._service.execute_scene(scene_id)
        return result == {"status": "success"}

    async def generate_tokens(
        self, client_id: str, client_secret: str, refresh_token: str
    ) -> OAuthToken:
        """Generate a new refresh/access token pair."""
        result = await self._service.generate_tokens(
            client_id, client_secret, refresh_token
        )
        return OAuthToken(self._service, result)
