"""Exception types raised by CycloneStore and its domain modules.

These are part of the public API — callers (API endpoints) catch them
and translate to HTTP 409 Conflict responses.
"""

from datetime import datetime


class AlreadyMatchedError(Exception):
    """Raised by ``CycloneStore.manual_match`` when the claim is already paired.

    The claim's ``matched_remittance_id`` is set, so any new pairing would
    clobber an existing match. Callers (the T15 API endpoint) should surface
    this as a 409 Conflict.
    """


class NotMatchedError(Exception):
    """Raised by ``CycloneStore.manual_unmatch`` when the claim has no match.

    Mirrors ``AlreadyMatchedError`` for the unpair operation. Same HTTP
    treatment: 409 Conflict at the API layer.
    """


class InvalidStateError(Exception):
    """Raised when an apply_* pure fn returns a skipped ApplyIntent.

    ``reconcile.apply_payment`` / ``apply_reversal`` may return
    ``skipped=True`` (e.g. claim already in a terminal state, or reversal
    on a non-paid claim). The store surfaces that as ``InvalidStateError``
    rather than silently pairing. The T15 API endpoint maps this to a
    409 Conflict and echoes ``current_state`` and ``activity_kind`` so
    the UI can render a precise message.
    """

    def __init__(self, current_state: str, activity_kind: str = "invalid_state"):
        self.current_state = current_state
        self.activity_kind = activity_kind
        super().__init__(
            f"invalid state {current_state} for apply (kind={activity_kind})"
        )


class DuplicateClaimError(Exception):
    """Raised by ``cyclone.store.submission_dedup.check_duplicate`` when a claim_id
    is re-submitted within the 30-day dedup window (SP41).

    Mirrors the other 409-class exceptions: callers catch it at the API
    boundary and surface it as a 409 Conflict. Carries
    ``original_submission_at`` so the UI can render "already submitted on
    YYYY-MM-DD" without re-querying.

    Note: the "30-day" literal in the message mirrors
    ``cyclone.store.submission_dedup.DEFAULT_WINDOW_DAYS`` — keep them in
    sync if either ever moves.
    """

    def __init__(self, claim_id: str, original_submission_at: datetime):
        self.claim_id = claim_id
        self.original_submission_at = original_submission_at
        super().__init__(
            f"claim_id {claim_id!r} was already submitted at "
            f"{original_submission_at.isoformat()}; within 30-day window"
        )
