"""The schemas module.

This module provides the following classes:
- CacheData
- CachePolicy
- EndpointType
"""

__all__ = ["CacheData", "CachePolicy", "EndpointType"]

from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from enum import Enum, auto
from typing import Annotated

from pydantic import Field

from seagrin.schemas._base import BaseModel


class EndpointType(Enum):
    """Categorises an API endpoint for the purposes of cache-freshness checks.

    Attributes:
        ITEM: A single-resource endpoint whose freshness is estimated from the age of the cached
            response relative to its `Last-Modified` date.
        LIST: A collection endpoint whose freshness is determined by a fixed expiry window.
    """

    ITEM = auto()
    LIST = auto()


class CachePolicy(BaseModel):
    """Configuration that controls how long cached responses are considered fresh.

    Attributes:
        percent: Fraction of the age of a cached `ITEM` response (measured from its `Last-Modified`
            date to the time it was fetched) that must elapse before the entry is re-validated.
            Must be between `0.0` and `1.0`.
            Defaults to `0.1` (10 %).
        expiry: Maximum age of a cached `LIST` response before it is considered stale.
            Defaults to 14 days.
    """

    percent: Annotated[float, Field(gt=0.0, lt=1.0)] = 0.1
    expiry: timedelta = timedelta(days=14)


@dataclass
class CacheData:
    """A single entry stored in the cache.

    Attributes:
        url: The URL that was requested, used as the cache key.
        response: The serialised response body returned by the server.
        timestamp: The UTC datetime at which the response was fetched and stored.
        last_modified: The `Last-Modified` datetime reported by the server for this resource.
    """

    url: str
    response: str
    timestamp: datetime
    last_modified: datetime

    def _next_check(self, percent: float) -> datetime:
        delta = self.timestamp.timestamp() - self.last_modified.timestamp()
        if delta <= 0:
            return self.last_modified
        window = timedelta(seconds=max(delta * percent, 1))
        return self.timestamp + window

    def is_fresh(
        self, policy: CachePolicy | None, endpoint_type: EndpointType, now: datetime | None = None
    ) -> bool:
        """Determine whether this cached entry is still fresh under the given policy.

        Freshness is evaluated differently depending on *endpoint_type*:
          - `EndpointType.ITEM` - the entry is fresh while *now* is earlier than the next scheduled
            re-validation time, which is calculated as a *percent*-sized fraction of the resource's
            age.
          - `EndpointType.LIST` - the entry is fresh while *now* is within the fixed *expiry*
            window measured from `timestamp`.

        Args:
            policy: The cache policy to apply.
              Passing `None` unconditionally treats the entry as fresh and skips all checks.
            endpoint_type: Whether this entry represents a single resource or a collection, which
              determines the freshness strategy used.
            now: The current UTC datetime used as the reference point for all comparisons.
              Defaults to `datetime.now` in UTC when omitted.

        Returns:
            `True` if the entry is considered fresh, `False` if it should be re-validated.

        Raises:
            ValueError: If *endpoint_type* is not a recognised `EndpointType` member.
        """
        if policy is None:
            return True
        now = now or datetime.now(tz=timezone.utc)
        if endpoint_type is EndpointType.ITEM:
            return now < self._next_check(percent=policy.percent)
        if endpoint_type is EndpointType.LIST:
            return now < (self.timestamp + policy.expiry)
        raise ValueError(f"Unknown EndpointType: '{endpoint_type}'")
