package store

import (
	"context"
	"errors"
	"testing"

	"github.com/tech/sendico/fx/storage/model"
	"github.com/tech/sendico/pkg/db/repository/builder"
	rd "github.com/tech/sendico/pkg/db/repository/decoder"
	"github.com/tech/sendico/pkg/db/storable"
	"github.com/tech/sendico/pkg/merrors"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

func TestPairStoreListEnabled(t *testing.T) {
	repo := &repoStub{
		findManyFn: func(ctx context.Context, _ builder.Query, decode rd.DecodingFunc) error {
			docs := []interface{}{
				&model.Pair{Pair: model.CurrencyPair{Base: "USD", Quote: "EUR"}},
			}
			return runDecoderWithDocs(ctx, t, decode, docs...)
		},
	}
	store := &pairStore{logger: zap.NewNop(), repo: repo}

	pairs, err := store.ListEnabled(context.Background())
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(pairs) != 1 || pairs[0].Pair.Base != "USD" {
		t.Fatalf("unexpected pairs result: %+v", pairs)
	}
}

func TestPairStoreGetInvalid(t *testing.T) {
	store := &pairStore{logger: zap.NewNop(), repo: &repoStub{}}
	if _, err := store.Get(context.Background(), model.CurrencyPair{}); !errors.Is(err, merrors.ErrInvalidArg) {
		t.Fatalf("expected invalid argument error")
	}
}

func TestPairStoreGetNotFound(t *testing.T) {
	repo := &repoStub{
		findOneFn: func(context.Context, builder.Query, storable.Storable) error {
			return merrors.ErrNoData
		},
	}
	store := &pairStore{logger: zap.NewNop(), repo: repo}

	if _, err := store.Get(context.Background(), model.CurrencyPair{Base: "USD", Quote: "EUR"}); !errors.Is(err, merrors.ErrNoData) {
		t.Fatalf("expected ErrNoData, got %v", err)
	}
}

func TestPairStoreUpsertInsert(t *testing.T) {
	ctx := context.Background()
	var inserted *model.Pair
	repo := &repoStub{
		findOneFn: func(context.Context, builder.Query, storable.Storable) error {
			return merrors.ErrNoData
		},
		insertFn: func(_ context.Context, obj storable.Storable, _ builder.Query) error {
			inserted = clonePair(t, obj)
			return nil
		},
	}
	store := &pairStore{logger: zap.NewNop(), repo: repo}
	pair := &model.Pair{Pair: model.CurrencyPair{Base: "USD", Quote: "EUR"}}
	if err := store.Upsert(ctx, pair); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if inserted == nil {
		t.Fatalf("expected insert to be called")
	}
}

func TestPairStoreUpsertUpdate(t *testing.T) {
	ctx := context.Background()
	var updated *model.Pair
	repo := &repoStub{
		findOneFn: func(_ context.Context, _ builder.Query, result storable.Storable) error {
			pair := result.(*model.Pair)
			pair.SetID(bson.NewObjectID())
			return nil
		},
		updateFn: func(_ context.Context, obj storable.Storable) error {
			updated = clonePair(t, obj)
			return nil
		},
	}
	store := &pairStore{logger: zap.NewNop(), repo: repo}

	if err := store.Upsert(ctx, &model.Pair{Pair: model.CurrencyPair{Base: "USD", Quote: "EUR"}}); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if updated == nil || updated.GetID() == nil {
		t.Fatalf("expected update to preserve existing ID")
	}
}
