"""Define a client to interact with SmartCocoon"""
import asyncio
import logging
from datetime import datetime, timedelta
from typing import Any, Optional, cast

import async_timeout
from aiohttp import ClientSession, ClientTimeout
from aiohttp.client_exceptions import ClientError

from pysmartcocoon.const import (
    API_AUTH_URL,
    API_FANS_URL,
    API_HEADERS,
    API_URL,
    DEFAULT_TIMEOUT,
    EntityType,
    FanMode,
)
from pysmartcocoon.fan import Fan
from pysmartcocoon.location import Location
from pysmartcocoon.room import Room
from pysmartcocoon.thermostat import Thermostat

_LOGGER: logging.Logger = logging.getLogger(__name__)


class Client:
    """Define the main controller class to communicate with the SmartCocoon cloud API"""

    def __init__(
        self,
        session: Optional[ClientSession] = None,
        request_timeout: int = DEFAULT_TIMEOUT,
    ) -> None:

        self._session = session
        self._request_timeout = request_timeout

        self._headersAuth = API_HEADERS
        self._authenticated = False

        """Variables to store SmartCocoon response data"""
        self.locations: dict[int, Location] = {}
        self.thermostats: dict[int, Thermostat] = {}
        self.rooms: dict[int, Room] = {}
        self.fans: dict[int, Fan] = {}

    async def authenticate(self, username: str, password: str) -> bool:

        self._authenticated = False

        # Authenticate with user and pass
        request_body = {}
        request_body.setdefault("json", {})
        request_body["json"]["email"] = username
        request_body["json"]["password"] = password

        await self._request("POST", API_AUTH_URL, **request_body)

        return self._authenticated

    async def _request(self, method: str, url: str, **kwargs) -> dict:
        """Make a request using token authentication.
        Args:
            method: Method for the HTTP request (example "GET" or "POST").
            path: path of the REST API endpoint.
        Returns:
            the Response object corresponding to the result of the API request.
        """

        use_running_session = self._session and not self._session.closed

        if use_running_session:
            session = self._session
        else:
            session = ClientSession(timeout=ClientTimeout(total=DEFAULT_TIMEOUT))

        assert session

        try:
            async with async_timeout.timeout(self._request_timeout):
                async with session.request(
                    method, url, ssl=False, headers=self._headersAuth, **kwargs
                ) as response:
                    response.raise_for_status()
                    data = await response.json(content_type=None)
        except ClientError as err:
            if "401" in str(err):
                # Authentication failed
                return None
        except asyncio.TimeoutError:
            _LOGGER.error("API call to SmartCocoon timed out")
            return None
        finally:
            if not use_running_session:
                await session.close()

        # If this request is for authorization, save auth data
        if url == API_AUTH_URL:
            self._bearerToken: str = response.headers["access-token"]
            self._bearerTokenExpiration: datetime = datetime.now() + timedelta(
                seconds=int(response.headers["expiry"]) - 10
            )
            self._apiClient: str = response.headers["client"]

            self._headersAuth["access-token"] = self._bearerToken
            self._headersAuth["client"] = self._apiClient
            self._headersAuth["uid"] = data["data"]["email"]

            self._user_id: str = data["data"]["id"]
            self._authenticated = True

        return cast(dict[str, Any], data)

    async def load_data(self) -> None:
        tasks = []
        tasks.append(self._load_locations())
        tasks.append(self._load_thermostats())
        tasks.append(self._load_rooms())
        tasks.append(self._load_fans())

        await asyncio.gather(*tasks)

    async def _load_locations(self) -> dict[int, Location]:
        # Init locations
        self.locations: dict[int, Location] = {}

        entity = EntityType.LOCATIONS.value
        response = await self._request("GET", f"{API_URL}{entity}")

        if len(response) != 0:
            for item in response[entity]:
                location = Location(data=item)
                self.locations[location.id] = location

        return self.locations

    async def _load_thermostats(self) -> dict[int, Thermostat]:

        # Init thermostats
        self.thermostats: dict[int, Thermostat] = {}

        entity = EntityType.THERMOSTATS.value
        response = await self._request("GET", f"{API_URL}{entity}")

        if len(response) != 0:
            for item in response[entity]:
                thermostat = Thermostat(data=item)
                self.thermostats[thermostat.id] = thermostat

        return self.thermostats

    async def _load_rooms(self) -> dict[int, Room]:

        # Init rooms
        self.rooms: dict[int, Room] = {}

        entity = EntityType.ROOMS.value
        response = await self._request("GET", f"{API_URL}{entity}")

        if len(response) != 0:
            for item in response[entity]:
                room = Room(data=item)
                self.rooms[room.id] = room

        return self.rooms

    async def _load_fans(self) -> dict[int, Fan]:

        # Init fans
        self.fans: dict[int, Fan] = {}

        entity = EntityType.FANS.value
        response = await self._request("GET", f"{API_URL}{entity}")

        if len(response) != 0:
            for item in response[entity]:
                fan = Fan(data=item)
                self.fans[fan.fan_id] = fan

        return self.fans

    async def _set_fan_mode(self, fan_id: str, fan_mode: FanMode) -> None:
        """Set the fan mode."""

        request_body = {}
        request_body.setdefault("json", {})
        request_body["json"]["mode"] = fan_mode.value

        fan = self.fans[fan_id]
        await self._request("PUT", f"{API_FANS_URL}{fan.id}", **request_body)

        if fan_mode == FanMode.ON:
            fan.fan_on = True
        elif fan_mode == FanMode.OFF:
            fan.fan_on = False

    async def fan_turn_on(self, fan_id: str) -> None:
        """Turn on fan."""
        await self._set_fan_mode(fan_id, FanMode.ON)

        _LOGGER.debug("Fan %s was turned on", fan_id)

    async def fan_turn_off(self, fan_id: str) -> None:
        """Turn on fan."""
        await self._set_fan_mode(fan_id, FanMode.OFF)

        _LOGGER.debug("Fan %s was turned off", fan_id)

    async def fan_auto(self, fan_id: str) -> None:
        """Enable auto mode on fan."""
        await self._set_fan_mode(fan_id, FanMode.AUTO)

        _LOGGER.debug("Fan %s was set to auto", fan_id)

    async def fan_eco(self, fan_id: str) -> None:
        """Enable eco mode on fan."""
        await self._set_fan_mode(fan_id, FanMode.ECO)

        _LOGGER.debug("Fan %s was set to eco", fan_id)

    async def fan_set_speed(self, fan_id: str, fan_speed: int) -> None:
        """Set fan speed."""

        if fan_speed > 100:
            _LOGGER.debug(
                "Fan speed of %s is invalid, must be between 0 and 100", fan_speed
            )
            return

        request_body = {}
        request_body.setdefault("json", {})
        request_body["json"]["power"] = fan_speed * 100

        fan = self.fans[fan_id]
        await self._request("PUT", f"{API_FANS_URL}{fan.id}", **request_body)

        _LOGGER.debug("Fan %s speed was set to %s%", fan_id, fan_speed)
