from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, Dict, Iterable, Optional


class RetriableError(Exception):
    """Raised when a transient connector failure should be retried."""


@dataclass(frozen=True)
class FetchResult:
    items: Iterable[Dict[str, Any]]
    next_token: Optional[str] = None
    batch_id: Optional[str] = None


class ConnectorBase(ABC):
    """Abstract base class all ingestion connectors must implement."""

    RetriableError = RetriableError

    @abstractmethod
    def fetch(
        self,
        *,
        batch_id: Optional[str] = None,
        next_token: Optional[str] = None,
        resume: bool = False,
    ) -> FetchResult:
        """Return a batch of raw source events plus an optional resume token."""
        raise NotImplementedError

    @abstractmethod
    def process(self, *, item: Dict[str, Any]) -> Dict[str, Any]:
        """Transform a raw item into normalized output (entity/evidence blobs)."""
        raise NotImplementedError

    def checkpoint_key(self) -> str:
        """Stable key used by the checkpoint store."""
        return self.__class__.__name__
