from __future__ import annotations

from dataclasses import dataclass
from typing import Literal


@dataclass(frozen=True)
class RetryPolicy:
    max_attempts: int = 1
    delay_seconds: float = 0.0
    backoff: Literal["constant", "linear", "exponential"] = "constant"
    retry_on: tuple[type[BaseException], ...] = (Exception,)

    def __post_init__(self):
        if self.max_attempts < 1:
            raise ValueError("max_attempts must be at least 1")
        if self.delay_seconds < 0:
            raise ValueError("delay_seconds must be >= 0")
        if self.backoff not in {"constant", "linear", "exponential"}:
            raise ValueError("backoff must be constant, linear, or exponential")
        if not self.retry_on:
            raise ValueError("retry_on must not be empty")

    @classmethod
    def from_value(cls, value: "RetryPolicy | dict | None") -> "RetryPolicy":
        if value is None:
            return cls()
        if isinstance(value, cls):
            return value
        if isinstance(value, dict):
            return cls(**value)
        raise TypeError("retry must be a RetryPolicy, dict, or None")

    def should_retry(self, exc: BaseException, attempt: int) -> bool:
        return attempt < self.max_attempts and isinstance(exc, self.retry_on)

    def delay_for_attempt(self, attempt: int) -> float:
        if self.delay_seconds == 0:
            return 0.0
        if self.backoff == "constant":
            return self.delay_seconds
        if self.backoff == "linear":
            return self.delay_seconds * attempt
        return self.delay_seconds * (2 ** (attempt - 1))


@dataclass(frozen=True)
class RunPolicies:
    max_events_processed: int = 1000
    max_task_invocations: int = 1000
    max_invocations_per_task: int = 100
    run_timeout_seconds: float | None = None

    def __post_init__(self):
        if self.max_events_processed < 1:
            raise ValueError("max_events_processed must be at least 1")
        if self.max_task_invocations < 1:
            raise ValueError("max_task_invocations must be at least 1")
        if self.max_invocations_per_task < 1:
            raise ValueError("max_invocations_per_task must be at least 1")
        if self.run_timeout_seconds is not None and self.run_timeout_seconds <= 0:
            raise ValueError("run_timeout_seconds must be > 0 when provided")
