"""One-call direct structured-result orchestration owned by ARR."""

from __future__ import annotations

from typing import Any, Protocol

from arr_ingestion.contracts import IngestionError
from arr_ingestion.direct_contracts import (
    DirectSubmissionReceipt,
    DirectSubmissionRequest,
    ReceivedDirectSubmission,
    SubmissionGrant,
    VerifiedDirectSubmission,
)


class DirectSubmissionRepository(Protocol):
    def assert_ready(self) -> None:
        ...

    def issue_grant(
        self,
        job_id: str,
        attempt_no: int,
        *,
        ttl_seconds: int,
    ) -> SubmissionGrant:
        ...

    def receive(
        self,
        request: DirectSubmissionRequest,
    ) -> ReceivedDirectSubmission:
        ...

    def commit(
        self,
        verified: VerifiedDirectSubmission,
    ) -> DirectSubmissionReceipt:
        ...

    def reject(
        self,
        submission: ReceivedDirectSubmission,
        failure_code: str,
    ) -> None:
        ...

    def expire_stale(self, *, limit: int) -> int:
        ...


class DirectSubmissionValidator(Protocol):
    def validate(
        self,
        submission: ReceivedDirectSubmission,
    ) -> VerifiedDirectSubmission:
        ...


_RETRYABLE_VALIDATION_CODES = frozenset(
    {
        "ARTIFACT_NOT_FOUND",
        "ARTIFACT_UNREADABLE",
        "DIRECT_REPLAY_UNAVAILABLE",
        "OBJECT_STORE_UNAVAILABLE",
    }
)


class DirectSubmissionService:
    """Persist, replay, and atomically commit one complete structured payload."""

    def __init__(
        self,
        validator: DirectSubmissionValidator,
        repository: DirectSubmissionRepository,
    ) -> None:
        self._validator = validator
        self._repository = repository

    def issue_grant(
        self,
        job_id: str,
        attempt_no: int,
        *,
        ttl_seconds: int = 900,
    ) -> SubmissionGrant:
        return self._repository.issue_grant(
            job_id,
            attempt_no,
            ttl_seconds=ttl_seconds,
        )

    def assert_ready(self) -> None:
        self._repository.assert_ready()

    def submit(self, raw_request: Any) -> DirectSubmissionReceipt:
        request = DirectSubmissionRequest.from_dict(raw_request)
        submission = self._repository.receive(request)
        if submission.receipt is not None:
            return submission.receipt
        try:
            verified = self._validator.validate(submission)
        except IngestionError as error:
            if (
                not error.retryable
                and error.code not in _RETRYABLE_VALIDATION_CODES
            ):
                self._repository.reject(submission, error.code)
            raise
        return self._repository.commit(verified)

    def expire_stale(self, *, limit: int = 100) -> int:
        if (
            not isinstance(limit, int)
            or isinstance(limit, bool)
            or not 1 <= limit <= 1000
        ):
            raise IngestionError(
                "DIRECT_SUBMISSION_INVALID",
                "direct submission expiry limit is invalid",
            )
        return self._repository.expire_stale(limit=limit)
