#
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import time

from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Literal, ParamSpec

from ahriman.models.metrics_timer import MetricsTimer


Params = ParamSpec("Params")


@dataclass(frozen=True)
class WaiterResult:
    """
    representation of a waiter result. This class should not be used directly, use derivatives instead

    Attributes:
        took(float): consumed time in seconds
    """

    took: float

    def __bool__(self) -> bool:
        """
        indicates whether the waiter completed with success or not

        Raises:
            NotImplementedError: not implemented method
        """
        raise NotImplementedError

    def __float__(self) -> float:
        """
        extract time spent to retrieve the result in seconds

        Returns:
            float: consumed time in seconds
        """
        return self.took


class WaiterTaskFinished(WaiterResult):
    """
    a waiter result used to notify that the task has been completed successfully
    """

    def __bool__(self) -> Literal[True]:
        """
        indicates whether the waiter completed with success or not

        Returns:
            Literal[True]: always ``True``
        """
        return True


class WaiterTimedOut(WaiterResult):
    """
    a waiter result used to notify that the waiter run out of time
    """

    def __bool__(self) -> Literal[False]:
        """
        indicates whether the waiter completed with success or not

        Returns:
            Literal[False]: always ``False``
        """
        return False


@dataclass(frozen=True)
class Waiter:
    """
    simple waiter implementation

    Attributes:
        interval(float): interval in seconds between checks
        wait_timeout(float): timeout in seconds to wait for. Negative value will result in immediate exit. Zero value
            means infinite timeout
    """

    wait_timeout: float
    interval: float = field(default=10, kw_only=True)

    def is_timed_out(self, elapsed: float) -> bool:
        """
        check if timer is out

        Attributes:
            elapsed(float): elapsed time in seconds

        Returns:
            bool: ``True`` in case current monotonic time is more than :attr:`start_time` and :attr:`wait_timeout`
            doesn't equal to 0
        """
        return self.wait_timeout != 0 and elapsed > self.wait_timeout

    def wait(self, in_progress: Callable[Params, bool], *args: Params.args, **kwargs: Params.kwargs) -> WaiterResult:
        """
        wait until requirements are not met

        Args:
            in_progress(Callable[Params, bool]): function to check if timer should wait for another cycle
            *args(Params.args): positional arguments for check call
            **kwargs(Params.kwargs): keyword arguments for check call

        Returns:
            WaiterResult: waiter result object
        """
        with MetricsTimer() as timer:
            while not (timed_out := self.is_timed_out(timer.elapsed)) and in_progress(*args, **kwargs):
                time.sleep(self.interval)
            took = timer.elapsed

        if timed_out:
            return WaiterTimedOut(took)
        return WaiterTaskFinished(took)
