from typing import Optional, Sequence

from international_payroll.modules.remittances.internal.enums import IntentStatus

_IN_PROGRESS_STATUSES = frozenset(
    [
        IntentStatus.INIT,
        IntentStatus.PREPARING,
        IntentStatus.PAYLOAD_GENERATED,
        IntentStatus.SUBMITTED,
        IntentStatus.CANCELLED,
        IntentStatus.CRASHED_MAYBE_PROCESSED,
        IntentStatus.SUBMITTED_TO_EXTERNAL_SYSTEM,
    ]
)

_AUTOMATIC_RETRYABLE_STATUSES = frozenset(
    [
        IntentStatus.MISSING_CONFIGURATION,
        IntentStatus.NON_SUFFICIENT_FUNDS,
    ]
)


_MANUAL_RETRYABLE_STATUSES = frozenset(
    [
        IntentStatus.FAILED_TO_INIT,
        IntentStatus.FAILED_TO_SUBMIT,
        IntentStatus.RETURNED,
        *list(_AUTOMATIC_RETRYABLE_STATUSES),
    ]
)

_COMPLETED_STATUSES = frozenset(
    [
        IntentStatus.COMPLETED,
        IntentStatus.COMPLETED_BY_EXTERNAL_SYSTEM,
        IntentStatus.REFUNDED,  # todo: this can probably go here as the remittance is complete.
    ]
)

_EXTERNAL_SYSTEM_STATUSES = frozenset(
    [
        IntentStatus.SUBMITTED_TO_EXTERNAL_SYSTEM,
        IntentStatus.COMPLETED_BY_EXTERNAL_SYSTEM,
    ]
)

# the intent is blocked on an external system or admin.
_BLOCKED_ON_EXTERNAL_STATUSES = [
    IntentStatus.MISSING_CONFIGURATION,
    IntentStatus.NON_SUFFICIENT_FUNDS,
]

_JUST_CREATED_STATUS = IntentStatus.CREATED

_PAYMENT_PROCESS_INITIALIZED_STATUS = IntentStatus.INIT
_FAILED_TO_INITIALIZE_PAYMENT_STATUS = IntentStatus.FAILED_TO_INIT

_PAYMENT_ATTEMPT_CREATED_STATUS = IntentStatus.PREPARING

_PAYMENT_SUBMITTED_STATUS = IntentStatus.SUBMITTED
_SUBMITTED_EXTERNALLY_STATUS = IntentStatus.SUBMITTED_TO_EXTERNAL_SYSTEM
_PAYMENT_SUBMITTED_STATUSES = frozenset(
    [
        IntentStatus.SUBMITTED,
        IntentStatus.SUBMITTED_TO_EXTERNAL_SYSTEM,
    ]
)

_FAILED_TO_SUBMIT_PAYMENT_STATUS = IntentStatus.FAILED_TO_SUBMIT

_RETURNED_STATUS = IntentStatus.RETURNED
_CANCELLED_STATUS = IntentStatus.CANCELLED
_REFUNDED_STATUS = IntentStatus.REFUNDED
_COMPLETED_INTERNALLY_STATUS = IntentStatus.COMPLETED
_COMPLETED_EXTERNALLY_STATUS = IntentStatus.COMPLETED_BY_EXTERNAL_SYSTEM

_MISSING_CONFIGURATION_STATUS = IntentStatus.MISSING_CONFIGURATION

_NON_SUFFICIENT_FUNDS_STATUS = IntentStatus.NON_SUFFICIENT_FUNDS

_STATUS_TO_ALLOWED_STATUS_TRANSITIONS = {
    None: frozenset(
        [
            IntentStatus.CANCELLED,
        ]
    ),
    IntentStatus.CREATED: frozenset(
        [
            IntentStatus.INIT,
            IntentStatus.CANCELLED,
            IntentStatus.SUBMITTED_TO_EXTERNAL_SYSTEM,
            IntentStatus.MISSING_CONFIGURATION,
            IntentStatus.NON_SUFFICIENT_FUNDS,
        ]
    ),
    IntentStatus.INIT: frozenset(
        [
            IntentStatus.PREPARING,
            IntentStatus.FAILED_TO_INIT,
        ]
    ),
    IntentStatus.PREPARING: frozenset(
        [
            IntentStatus.FAILED_TO_SUBMIT,
            IntentStatus.PAYLOAD_GENERATED,
            IntentStatus.CANCELLED,
        ]
    ),
    IntentStatus.PAYLOAD_GENERATED: frozenset(
        [
            IntentStatus.SUBMITTED,
            IntentStatus.FAILED_TO_SUBMIT,
        ]
    ),
    IntentStatus.SUBMITTED: frozenset(
        [
            IntentStatus.COMPLETED,
            IntentStatus.REFUNDED,
            IntentStatus.RETURNED,
            IntentStatus.CRASHED_MAYBE_PROCESSED,
            IntentStatus.CANCELLED,
        ]
    ),
    IntentStatus.FAILED_TO_INIT: frozenset(
        [
            IntentStatus.INIT,
            IntentStatus.SUBMITTED_TO_EXTERNAL_SYSTEM,
        ]
    ),
    IntentStatus.FAILED_TO_SUBMIT: frozenset(
        [
            IntentStatus.INIT,
            IntentStatus.SUBMITTED_TO_EXTERNAL_SYSTEM,
        ]
    ),
    IntentStatus.SUBMITTED_TO_EXTERNAL_SYSTEM: frozenset(
        [
            IntentStatus.COMPLETED_BY_EXTERNAL_SYSTEM,
            IntentStatus.REFUNDED,
            IntentStatus.RETURNED,
            IntentStatus.CANCELLED,
        ]
    ),
    IntentStatus.RETURNED: frozenset(
        [
            IntentStatus.INIT,
        ]
    ),
    IntentStatus.CRASHED_MAYBE_PROCESSED: frozenset(
        [IntentStatus.RETURNED, IntentStatus.COMPLETED, IntentStatus.REFUNDED, IntentStatus.CANCELLED]
    ),
    IntentStatus.MISSING_CONFIGURATION: frozenset(
        [IntentStatus.INIT, IntentStatus.NON_SUFFICIENT_FUNDS, IntentStatus.CREATED]
    ),
    IntentStatus.NON_SUFFICIENT_FUNDS: frozenset(
        [IntentStatus.INIT, IntentStatus.MISSING_CONFIGURATION, IntentStatus.CREATED]
    ),
}


class IntentStatusUtil:
    @classmethod
    def isTransitionValid(cls, currentState: Optional[IntentStatus], nextState: IntentStatus) -> bool:
        return nextState in _STATUS_TO_ALLOWED_STATUS_TRANSITIONS.get(currentState, [])

    @classmethod
    def isCompleted(cls, status: IntentStatus) -> bool:
        return status in _COMPLETED_STATUSES

    @classmethod
    def isCompletedInternally(cls, status: IntentStatus) -> bool:
        return status == _COMPLETED_INTERNALLY_STATUS

    @classmethod
    def isBeingHandledExternally(cls, status: IntentStatus) -> bool:
        return status in _EXTERNAL_SYSTEM_STATUSES

    @classmethod
    def getSuccesfulExternalRemittanceStatus(cls) -> IntentStatus:
        return _COMPLETED_EXTERNALLY_STATUS

    @classmethod
    def getSuccesfulInternalRemittanceStatus(cls) -> IntentStatus:
        return _COMPLETED_INTERNALLY_STATUS

    @classmethod
    def getSuccessfulRefundStatus(cls) -> IntentStatus:
        return _REFUNDED_STATUS

    @classmethod
    def getCancelledStatus(cls) -> IntentStatus:
        return _CANCELLED_STATUS

    @classmethod
    def isInProgress(cls, status: IntentStatus) -> bool:
        return status in _IN_PROGRESS_STATUSES

    @classmethod
    def isAutomaticRetryAllowed(cls, status: IntentStatus) -> bool:
        return status in _AUTOMATIC_RETRYABLE_STATUSES

    @classmethod
    def isManualRetryAllowed(cls, status: IntentStatus) -> bool:
        return status in _MANUAL_RETRYABLE_STATUSES

    @classmethod
    def isSuccessfullySubmittedInternally(cls, status: IntentStatus) -> bool:
        return status == _PAYMENT_SUBMITTED_STATUS

    @classmethod
    def getSuccessfulPaymentSubmittedInternallyStatus(cls) -> IntentStatus:
        return _PAYMENT_SUBMITTED_STATUS

    @classmethod
    def getSubmittedExternalRemittanceStatus(cls) -> IntentStatus:
        return _SUBMITTED_EXTERNALLY_STATUS

    @classmethod
    def isSubmittedExternally(cls, status: IntentStatus) -> bool:
        return status == _SUBMITTED_EXTERNALLY_STATUS

    @classmethod
    def isSuccessfullySubmitted(cls, status: IntentStatus) -> bool:
        return status in _PAYMENT_SUBMITTED_STATUSES

    @classmethod
    def isJustCreated(cls, status: IntentStatus) -> bool:
        return status == _JUST_CREATED_STATUS

    @classmethod
    def isBlockedOnExternalStatus(cls, status: IntentStatus) -> bool:
        return status in _BLOCKED_ON_EXTERNAL_STATUSES

    @classmethod
    def getJustCreatedStatus(cls) -> IntentStatus:
        return _JUST_CREATED_STATUS

    @classmethod
    def getPaymentProcessInitializedStatus(cls) -> IntentStatus:
        return _PAYMENT_PROCESS_INITIALIZED_STATUS

    @classmethod
    def getPaymentAttemptCreatedStatus(cls) -> IntentStatus:
        return _PAYMENT_ATTEMPT_CREATED_STATUS

    @classmethod
    def getFailedToInitializePaymentStatus(cls) -> IntentStatus:
        return _FAILED_TO_INITIALIZE_PAYMENT_STATUS

    @classmethod
    def getFailedToSubmitPaymentStatus(cls) -> IntentStatus:
        return _FAILED_TO_SUBMIT_PAYMENT_STATUS

    @classmethod
    def getReturnedStatus(cls) -> IntentStatus:
        return _RETURNED_STATUS

    @classmethod
    def getMissingConfigurationStatus(cls) -> IntentStatus:
        return _MISSING_CONFIGURATION_STATUS

    @classmethod
    def isMissingConfiguration(cls, status: IntentStatus) -> bool:
        return status == _MISSING_CONFIGURATION_STATUS

    @classmethod
    def isRetryable(cls, status: IntentStatus) -> bool:
        return cls.isAutomaticRetryAllowed(status) or cls.isManualRetryAllowed(status)

    @classmethod
    def isInProgressOrCompleted(cls, status: IntentStatus) -> bool:
        return cls.isInProgress(status) or cls.isCompleted(status)

    @classmethod
    def getInProgressAndCompletedStatuses(cls) -> Sequence[IntentStatus]:
        return list(_IN_PROGRESS_STATUSES.union(_COMPLETED_STATUSES))

    @classmethod
    def getAutomaticRetryableStatuses(cls) -> Sequence[IntentStatus]:
        return list(_AUTOMATIC_RETRYABLE_STATUSES)
