package quote_executability_classifier

import (
	"errors"
	"testing"

	"github.com/tech/sendico/pkg/merrors"
	quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
	"go.mongodb.org/mongo-driver/v2/bson"
)

func TestExtract_TypedReasonWrapper(t *testing.T) {
	base := merrors.InvalidArgument("x")
	err := WrapAmountTooSmall(base)

	reason, ok := Extract(err)
	if !ok {
		t.Fatalf("expected extracted reason")
	}
	if reason != quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_AMOUNT_TOO_SMALL {
		t.Fatalf("unexpected reason: %s", reason.String())
	}
	if !errors.Is(err, merrors.ErrInvalidArg) {
		t.Fatalf("expected wrapped error to preserve cause")
	}
}

func TestBlockReasonFromError(t *testing.T) {
	classifier := New()

	tests := []struct {
		name string
		err  error
		want quotationv2.QuoteBlockReason
	}{
		{
			name: "nil error",
			err:  nil,
			want: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED,
		},
		{
			name: "typed wrapper wins",
			err:  WrapInsufficientLiquidity(merrors.InvalidArgument("x")),
			want: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_INSUFFICIENT_LIQUIDITY,
		},
		{
			name: "no data fallback",
			err:  merrors.NoData("x"),
			want: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_ROUTE_UNAVAILABLE,
		},
		{
			name: "access denied fallback",
			err:  merrors.AccessDenied("payment", "execute", bson.NilObjectID),
			want: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_RISK_BLOCKED,
		},
		{
			name: "invalid arg fallback",
			err:  merrors.InvalidArgument("x"),
			want: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_ROUTE_UNAVAILABLE,
		},
		{
			name: "unknown error",
			err:  errors.New("boom"),
			want: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := classifier.BlockReasonFromError(tt.err)
			if got != tt.want {
				t.Fatalf("unexpected block reason: got=%s want=%s", got.String(), tt.want.String())
			}
		})
	}
}

func TestBuildState(t *testing.T) {
	classifier := New()

	activeExecutable := classifier.BuildState(
		false,
		quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED,
	)
	if got, want := activeExecutable.State(), quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE; got != want {
		t.Fatalf("unexpected state: got=%s want=%s", got.String(), want.String())
	}
	if got := activeExecutable.BlockReason(); got != quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED {
		t.Fatalf("expected empty block reason, got=%s", got.String())
	}

	blocked := classifier.BuildState(
		false,
		quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_PRICE_STALE,
	)
	if got, want := blocked.State(), quotationv2.QuoteState_QUOTE_STATE_BLOCKED; got != want {
		t.Fatalf("unexpected state: got=%s want=%s", got.String(), want.String())
	}
	if blocked.BlockReason() != quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_PRICE_STALE {
		t.Fatalf("unexpected block reason: %s", blocked.BlockReason().String())
	}

	indicative := classifier.BuildState(
		true,
		quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_ROUTE_UNAVAILABLE,
	)
	if got, want := indicative.State(), quotationv2.QuoteState_QUOTE_STATE_INDICATIVE; got != want {
		t.Fatalf("unexpected state: got=%s want=%s", got.String(), want.String())
	}
	if got := indicative.BlockReason(); got != quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED {
		t.Fatalf("expected empty block reason for indicative state, got=%s", got.String())
	}
}

func TestApply(t *testing.T) {
	classifier := New()
	quote := &quotationv2.PaymentQuote{}

	indicative := classifier.BuildState(
		true,
		quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED,
	)
	indicative.Apply(quote)
	if got, want := quote.GetState(), quotationv2.QuoteState_QUOTE_STATE_INDICATIVE; got != want {
		t.Fatalf("unexpected state: got=%s want=%s", got.String(), want.String())
	}

	executable := classifier.BuildState(
		false,
		quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED,
	)
	executable.Apply(quote)
	if got, want := quote.GetState(), quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE; got != want {
		t.Fatalf("unexpected state: got=%s want=%s", got.String(), want.String())
	}

	blocked := classifier.BuildState(
		false,
		quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_INSUFFICIENT_LIQUIDITY,
	)
	blocked.Apply(quote)
	if got, want := quote.GetState(), quotationv2.QuoteState_QUOTE_STATE_BLOCKED; got != want {
		t.Fatalf("unexpected state: got=%s want=%s", got.String(), want.String())
	}
	if got := quote.GetBlockReason(); got != quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_INSUFFICIENT_LIQUIDITY {
		t.Fatalf("unexpected block reason: %s", got.String())
	}
}
