"""Local Hub Authentication Manager."""

import logging
import time

from asyncio import Lock
from .auth_mgr import YoLinkAuthMgr
from aiohttp import ClientSession, ClientError, ClientTimeout
from json import JSONDecodeError
from asyncio import TimeoutError


from typing import cast

_LOGGER = logging.getLogger(__name__)

CLOCK_OUT_OF_SYNC_MAX_SEC = 20


class YoLinkLocalAuthMgr(YoLinkAuthMgr):
    """YoLink Local API Authentication Manager."""

    def __init__(
        self,
        session: ClientSession,
        token_url: str,
        client_id: str,
        client_secret: str,
    ) -> None:
        """Init YoLink Local Auth Manager."""
        super().__init__(session)
        self._token_url = token_url
        self._client_id: str = client_id
        self._client_secret: str = client_secret
        self._token: dict | None = None
        self._token_lock = Lock()

    def access_token(self) -> str | None:
        """Get auth token."""
        return self._token["access_token"] if self._token is not None else None

    @property
    def valid_token(self) -> bool:
        if self._token is None:
            return False
        return (
            cast(float, self._token["expires_at"])
            > time.time() + CLOCK_OUT_OF_SYNC_MAX_SEC
        )

    async def check_and_refresh_token(self) -> str | None:
        """Check and fresh token."""
        async with self._token_lock:
            if self.valid_token:
                return self.access_token()
            try:
                new_token = await self._token_request()
                new_token["expires_at"] = time.time() + new_token["expires_in"]
                self._token = new_token
                return self.access_token()
            except TimeoutError as err:
                raise ClientError("Token request timed out") from err

    async def _token_request(self) -> dict:
        """Make a token request."""
        resp = await self._session.post(
            url=self._token_url,
            headers={"Content-Type": "application/x-www-form-urlencoded"},
            data={
                "grant_type": "client_credentials",
                "scope": "create",
                "client_id": self._client_id,
                "client_secret": self._client_secret,
            },
            timeout=ClientTimeout(total=10),
        )
        if resp.status >= 400:
            try:
                error_response = await resp.json()
            except (ClientError, JSONDecodeError):
                error_response = {}
            error_code = error_response.get("error", "unknown")
            error_description = error_response.get("error_description", "unknown error")
            _LOGGER.error(
                "Token request failed (%s): %s",
                error_code,
                error_description,
            )
        resp.raise_for_status()
        return cast(dict, await resp.json())
