package quote_executability_classifier

import (
	"errors"

	"github.com/tech/sendico/pkg/merrors"
	quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
)

type blockReasonError struct {
	reason quotationv2.QuoteBlockReason
	cause  error
}

func (e *blockReasonError) Error() string {
	if e == nil {
		return ""
	}
	if e.cause == nil {
		return e.reason.String()
	}
	return e.cause.Error()
}

func (e *blockReasonError) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.cause
}

func (e *blockReasonError) BlockReason() quotationv2.QuoteBlockReason {
	if e == nil {
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED
	}
	return e.reason
}

func Wrap(err error, reason quotationv2.QuoteBlockReason) error {
	if err == nil {
		return nil
	}
	return &blockReasonError{
		reason: reason,
		cause:  err,
	}
}

func WrapRouteUnavailable(err error) error {
	return Wrap(err, quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_ROUTE_UNAVAILABLE)
}

func WrapLimitBlocked(err error) error {
	return Wrap(err, quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_LIMIT_BLOCKED)
}

func WrapRiskBlocked(err error) error {
	return Wrap(err, quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_RISK_BLOCKED)
}

func WrapInsufficientLiquidity(err error) error {
	return Wrap(err, quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_INSUFFICIENT_LIQUIDITY)
}

func WrapPriceStale(err error) error {
	return Wrap(err, quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_PRICE_STALE)
}

func WrapAmountTooSmall(err error) error {
	return Wrap(err, quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_AMOUNT_TOO_SMALL)
}

func WrapAmountTooLarge(err error) error {
	return Wrap(err, quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_AMOUNT_TOO_LARGE)
}

func Extract(err error) (quotationv2.QuoteBlockReason, bool) {
	var reasonErr interface {
		BlockReason() quotationv2.QuoteBlockReason
	}
	if errors.As(err, &reasonErr) {
		reason := reasonErr.BlockReason()
		if reason != quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED {
			return reason, true
		}
	}
	return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED, false
}

type QuoteState struct {
	state       quotationv2.QuoteState
	blockReason quotationv2.QuoteBlockReason
}

func (s QuoteState) State() quotationv2.QuoteState {
	return s.state
}

func (s QuoteState) BlockReason() quotationv2.QuoteBlockReason {
	if s.state != quotationv2.QuoteState_QUOTE_STATE_BLOCKED {
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED
	}
	return s.blockReason
}

func (s QuoteState) Apply(quote *quotationv2.PaymentQuote) {
	if quote == nil {
		return
	}
	quote.State = s.state
	quote.BlockReason = s.BlockReason()
}

type QuoteExecutabilityClassifier struct{}

func New() *QuoteExecutabilityClassifier {
	return &QuoteExecutabilityClassifier{}
}

func (c *QuoteExecutabilityClassifier) BuildState(
	previewOnly bool,
	blockReason quotationv2.QuoteBlockReason,
) QuoteState {
	if previewOnly {
		return QuoteState{
			state: quotationv2.QuoteState_QUOTE_STATE_INDICATIVE,
		}
	}
	if blockReason == quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED {
		return QuoteState{
			state: quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE,
		}
	}
	return QuoteState{
		state:       quotationv2.QuoteState_QUOTE_STATE_BLOCKED,
		blockReason: blockReason,
	}
}

func (c *QuoteExecutabilityClassifier) BlockReasonFromError(err error) quotationv2.QuoteBlockReason {
	if err == nil {
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED
	}

	if reason, ok := Extract(err); ok {
		return reason
	}

	switch {
	case errors.Is(err, merrors.ErrNoData):
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_ROUTE_UNAVAILABLE
	case errors.Is(err, merrors.ErrAccessDenied), errors.Is(err, merrors.ErrUnauthorized):
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_RISK_BLOCKED
	case errors.Is(err, merrors.ErrInvalidArg):
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_ROUTE_UNAVAILABLE
	default:
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED
	}
}
