from __future__ import annotations

from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal, Protocol, runtime_checkable


CAPABILITY_CONNECTORS_FEEDS = "connectors.feeds"
FeedFormat = Literal["rss", "atom"]
FeedVisibility = Literal["public", "tenant", "private"]


class FeedCapabilityError(ValueError):
    """Stable error raised by feed transport implementations."""


@dataclass(frozen=True, slots=True)
class FeedEntry:
    id: str
    title: str
    url: str | None = None
    summary: str | None = None
    content: str | None = None
    author: str | None = None
    published_at: datetime | None = None
    updated_at: datetime | None = None
    categories: tuple[str, ...] = ()
    enclosures: tuple[Mapping[str, object], ...] = ()
    visibility: FeedVisibility = "public"
    metadata: Mapping[str, object] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class FeedDocument:
    format: FeedFormat
    title: str
    source_url: str
    entries: tuple[FeedEntry, ...]
    description: str | None = None
    home_url: str | None = None
    language: str | None = None
    updated_at: datetime | None = None
    acquired_at: datetime | None = None
    fresh_until: datetime | None = None
    etag: str | None = None
    last_modified: str | None = None
    content_type: str | None = None
    sha256: str = ""
    metadata: Mapping[str, object] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class FeedRenderRequest:
    format: FeedFormat
    title: str
    feed_url: str
    home_url: str
    entries: tuple[FeedEntry, ...]
    description: str | None = None
    language: str | None = None
    allowed_visibilities: frozenset[FeedVisibility] = frozenset({"public"})


@dataclass(frozen=True, slots=True)
class FeedRenderResult:
    format: FeedFormat
    content_type: str
    body: bytes
    included_entries: int
    excluded_entries: int


@runtime_checkable
class FeedProvider(Protocol):
    def fetch(
        self,
        url: str,
        *,
        timeout: float = 15,
        max_entries: int = 2_000,
    ) -> FeedDocument:
        ...

    def parse(
        self,
        content: bytes,
        *,
        source_url: str,
        content_type: str | None = None,
        max_entries: int = 2_000,
    ) -> FeedDocument:
        ...

    def render(self, request: FeedRenderRequest) -> FeedRenderResult:
        ...


def feed_provider(registry: object | None) -> FeedProvider | None:
    if (
        registry is None
        or not hasattr(registry, "has_capability")
        or not hasattr(registry, "capability")
        or not registry.has_capability(CAPABILITY_CONNECTORS_FEEDS)
    ):
        return None
    provider = registry.capability(CAPABILITY_CONNECTORS_FEEDS)
    return provider if isinstance(provider, FeedProvider) else None


__all__ = [
    "CAPABILITY_CONNECTORS_FEEDS",
    "FeedCapabilityError",
    "FeedDocument",
    "FeedEntry",
    "FeedFormat",
    "FeedProvider",
    "FeedRenderRequest",
    "FeedRenderResult",
    "FeedVisibility",
    "feed_provider",
]
