package accountapiimp

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/model"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
	"github.com/tech/sendico/server/interface/api/srequest"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

type stubLedgerAccountClient struct {
	createReqs []*ledgerv1.CreateAccountRequest
	createResp *ledgerv1.CreateAccountResponse
	createErr  error
}

func (s *stubLedgerAccountClient) CreateAccount(_ context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) {
	s.createReqs = append(s.createReqs, req)
	return s.createResp, s.createErr
}

func (s *stubLedgerAccountClient) Close() error {
	return nil
}

func TestOpenOrgLedgerAccount(t *testing.T) {
	t.Run("creates operating ledger accounts for RUB and USDT", func(t *testing.T) {
		desc := "  Main org ledger account  "
		sr := &srequest.Signup{
			Account: model.AccountData{
				LoginData: model.LoginData{
					UserDataBase: model.UserDataBase{
						Login: "owner@example.com",
					},
				},
			},
			LedgerWallet: model.Describable{
				Name:        "  Primary Ledger  ",
				Description: &desc,
			},
		}

		org := &model.Organization{}
		org.SetID(bson.NewObjectID())

		ledgerStub := &stubLedgerAccountClient{
			createResp: &ledgerv1.CreateAccountResponse{
				Account: &ledgerv1.LedgerAccount{LedgerAccountRef: bson.NewObjectID().Hex()},
			},
		}
		api := &AccountAPI{
			logger:       zap.NewNop(),
			ledgerClient: ledgerStub,
			chainAsset: &chainv1.Asset{
				TokenSymbol: " usdt ",
			},
		}

		err := api.openOrgLedgerAccount(context.Background(), org, sr)
		assert.NoError(t, err)
		if assert.Len(t, ledgerStub.createReqs, 2) {
			currencies := make([]string, 0, len(ledgerStub.createReqs))
			for _, req := range ledgerStub.createReqs {
				currencies = append(currencies, req.GetCurrency())
				assert.Equal(t, org.ID.Hex(), req.GetOrganizationRef())
				assert.Equal(t, ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, req.GetAccountType())
				assert.Equal(t, ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE, req.GetStatus())
				assert.Equal(t, ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING, req.GetRole())
				assert.Equal(t, map[string]string{
					"source": "signup",
					"login":  "owner@example.com",
				}, req.GetMetadata())
				if assert.NotNil(t, req.GetDescribable()) {
					assert.Equal(t, "Primary Ledger", req.GetDescribable().GetName())
					if assert.NotNil(t, req.GetDescribable().Description) {
						assert.Equal(t, "Main org ledger account", req.GetDescribable().GetDescription())
					}
				}
			}
			assert.ElementsMatch(t, []string{"RUB", "USDT"}, currencies)
		}
	})

	t.Run("fails when ledger client is missing", func(t *testing.T) {
		api := &AccountAPI{
			logger: zap.NewNop(),
			chainAsset: &chainv1.Asset{
				TokenSymbol: "USDT",
			},
		}

		err := api.openOrgLedgerAccount(context.Background(), &model.Organization{}, &srequest.Signup{})
		assert.Error(t, err)
		assert.True(t, errors.Is(err, merrors.ErrInternal))
	})

	t.Run("fails when ledger response has empty reference", func(t *testing.T) {
		ledgerStub := &stubLedgerAccountClient{
			createResp: &ledgerv1.CreateAccountResponse{},
		}
		api := &AccountAPI{
			logger:       zap.NewNop(),
			ledgerClient: ledgerStub,
			chainAsset: &chainv1.Asset{
				TokenSymbol: "USDT",
			},
		}

		err := api.openOrgLedgerAccount(context.Background(), &model.Organization{}, &srequest.Signup{})
		assert.Error(t, err)
		assert.True(t, errors.Is(err, merrors.ErrInternal))
	})
}
