"""Module for common utility functions."""

from __future__ import annotations

import asyncio
import datetime
import logging
from contextlib import suppress
from functools import update_wrapper
from threading import Lock
from typing import TYPE_CHECKING, Any, Callable
from warnings import warn

from typing_extensions import ParamSpec, TypeVar

from ring_doorbell.exceptions import RingError

if TYPE_CHECKING:
    from collections.abc import Coroutine

    from .auth import Auth
    from .generic import RingGeneric
    from .group import RingLightGroup
    from .listen.eventlistener import RingEventListener
    from .ring import Ring

    _T = TypeVar(
        "_T", bound=Auth | Ring | RingGeneric | RingLightGroup | RingEventListener
    )
    _R = TypeVar("_R")
    _P = ParamSpec("_P")

_logger = logging.getLogger(__name__)


def parse_datetime(datetime_str: str) -> datetime.datetime:
    """Parse a datetime string into a datetime object.

    Ring api has inconsistent datetime string patterns.
    """
    # Check if the datetime string contains a period which precedes 'Z',
    # indicating microseconds
    if "." in datetime_str and datetime_str.endswith("Z"):
        # String contains microseconds and ends with 'Z'
        format_str = "%Y-%m-%dT%H:%M:%S.%fZ"
    else:
        # String does not contain microseconds, should end with 'Z'
        # Could be updated to handle other formats
        format_str = "%Y-%m-%dT%H:%M:%SZ"
    try:
        res = datetime.datetime.strptime(datetime_str, format_str).replace(
            tzinfo=datetime.timezone.utc
        )
    except ValueError:
        _logger.exception(
            "Unable to parse datetime string %s, defaulting to now time", datetime_str
        )
        res = datetime.datetime.now(datetime.timezone.utc)
    return res


class _DeprecatedSyncApiHandler:
    def __init__(self, auth: Auth) -> None:
        self.auth = auth
        self._sync_lock = Lock()

    async def run_and_close_session(
        self,
        async_method: Callable[_P, Coroutine[Any, Any, _R]],
        *args: _P.args,
        **kwargs: _P.kwargs,
    ) -> _R:
        try:
            self._sync_lock.acquire()
            res = await async_method(*args, **kwargs)
        finally:
            with suppress(Exception):
                await self.auth.async_close()
            self._sync_lock.release()

        return res

    @staticmethod
    def check_no_loop(classname: str, method_name: str) -> None:
        current_loop = None
        with suppress(RuntimeError):
            current_loop = asyncio.get_running_loop()
        if current_loop:
            msg = (
                f"You cannot call deprecated sync function {classname}.{method_name} "
                "from within a running event loop."
            )
            raise RingError(msg)

    def get_api_query(
        self,
        class_instance: _T,
        method_name: str,
    ) -> Any:
        """Return deprecated sync api query attribute."""
        classname = type(class_instance).__name__

        def _deprecated_sync_function(
            async_func: Callable[_P, Coroutine[Any, Any, _R]],
        ) -> Callable[_P, _R]:
            def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
                self.check_no_loop(classname, method_name)
                msg = (
                    f"{classname}.{method_name} is deprecated, use "
                    f"{classname}.{async_method_name}"
                )
                warn(msg, DeprecationWarning, stacklevel=1)
                return asyncio.run(
                    self.run_and_close_session(async_func, *args, **kwargs)
                )

            return update_wrapper(wrapper, async_func)

        async_method_name = f"async_{method_name}"
        async_method = getattr(class_instance, async_method_name)
        return _deprecated_sync_function(async_method)

    def get_api_property(
        self,
        class_instance: _T,
        method_name: str,
    ) -> Any:
        """Return deprecated sync api property value."""
        classname = type(class_instance).__name__
        self.check_no_loop(classname, method_name)
        async_method_name = f"async_get_{method_name}"
        msg = (
            f"{classname}.{method_name} is deprecated, use "
            f"{classname}.{async_method_name}"
        )
        warn(msg, DeprecationWarning, stacklevel=1)
        async_method = getattr(class_instance, async_method_name)
        return asyncio.run(self.run_and_close_session(async_method))

    def set_api_property(
        self,
        class_instance: _T,
        property_name: str,
        value: Any,
    ) -> None:
        """Set sync api property value."""
        classname = type(class_instance).__name__

        self.check_no_loop(classname, property_name)
        async_method_name = f"async_set_{property_name}"
        msg = (
            f"{classname}.{property_name} is deprecated, use "
            f"{classname}.{async_method_name}"
        )
        warn(msg, DeprecationWarning, stacklevel=1)
        async_method = getattr(class_instance, async_method_name)
        asyncio.run(self.run_and_close_session(async_method, value))
