package ledger

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/tech/sendico/ledger/storage"
	"github.com/tech/sendico/pkg/merrors"
	pmodel "github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/model/account_role"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

type systemAccountsStoreStub struct {
	created  []*pmodel.LedgerAccount
	existing map[string]*pmodel.LedgerAccount
}

func (s *systemAccountsStoreStub) Create(_ context.Context, account *pmodel.LedgerAccount) error {
	if account.GetID() == nil || account.GetID().IsZero() {
		account.SetID(bson.NewObjectID())
	}
	s.created = append(s.created, account)
	if s.existing == nil {
		s.existing = make(map[string]*pmodel.LedgerAccount)
	}
	if account.SystemPurpose != nil {
		key := string(*account.SystemPurpose) + "|" + account.Currency
		s.existing[key] = account
	}
	return nil
}

func (s *systemAccountsStoreStub) Get(context.Context, bson.ObjectID) (*pmodel.LedgerAccount, error) {
	return nil, storage.ErrAccountNotFound
}

func (s *systemAccountsStoreStub) GetByAccountCode(context.Context, bson.ObjectID, string, string) (*pmodel.LedgerAccount, error) {
	return nil, merrors.NotImplemented("get by code")
}

func (s *systemAccountsStoreStub) GetByRole(context.Context, bson.ObjectID, string, account_role.AccountRole) (*pmodel.LedgerAccount, error) {
	return nil, merrors.NotImplemented("get by role")
}

func (s *systemAccountsStoreStub) GetSystemAccount(_ context.Context, purpose pmodel.SystemAccountPurpose, currency string) (*pmodel.LedgerAccount, error) {
	if s.existing == nil {
		return nil, storage.ErrAccountNotFound
	}
	key := string(purpose) + "|" + currency
	if acc, ok := s.existing[key]; ok {
		return acc, nil
	}
	return nil, storage.ErrAccountNotFound
}

func (s *systemAccountsStoreStub) GetDefaultSettlement(context.Context, bson.ObjectID, string) (*pmodel.LedgerAccount, error) {
	return nil, merrors.NotImplemented("get default settlement")
}

func (s *systemAccountsStoreStub) ListByOrganization(context.Context, bson.ObjectID, *storage.AccountsFilter, int, int) ([]*pmodel.LedgerAccount, error) {
	return nil, merrors.NotImplemented("list")
}

func (s *systemAccountsStoreStub) UpdateStatus(context.Context, bson.ObjectID, pmodel.LedgerAccountStatus) error {
	return merrors.NotImplemented("update status")
}

type systemAccountsRepoStub struct {
	accounts storage.AccountsStore
}

func (r *systemAccountsRepoStub) Ping(context.Context) error                  { return nil }
func (r *systemAccountsRepoStub) Accounts() storage.AccountsStore             { return r.accounts }
func (r *systemAccountsRepoStub) JournalEntries() storage.JournalEntriesStore { return nil }
func (r *systemAccountsRepoStub) PostingLines() storage.PostingLinesStore     { return nil }
func (r *systemAccountsRepoStub) Balances() storage.BalancesStore             { return nil }
func (r *systemAccountsRepoStub) Outbox() storage.OutboxStore                 { return nil }

func TestEnsureSystemAccounts_CreatesAndCaches(t *testing.T) {
	originalCurrencies := pmodel.SupportedCurrencies
	pmodel.SupportedCurrencies = []pmodel.Currency{pmodel.CurrencyUSD, pmodel.CurrencyEUR}
	t.Cleanup(func() {
		pmodel.SupportedCurrencies = originalCurrencies
	})

	accounts := &systemAccountsStoreStub{}
	svc := &Service{
		logger:  zap.NewNop(),
		storage: &systemAccountsRepoStub{accounts: accounts},
	}

	require.NoError(t, svc.ensureSystemAccounts(context.Background()))
	require.Len(t, accounts.created, 4)

	for _, acc := range accounts.created {
		require.Equal(t, pmodel.LedgerAccountScopeSystem, acc.Scope)
		require.True(t, acc.AllowNegative)
		require.Nil(t, acc.OrganizationRef)
		require.Equal(t, account_role.AccountRole(""), acc.Role)
		require.NotNil(t, acc.SystemPurpose)
		require.Equal(t, pmodel.LedgerAccountTypeAsset, acc.AccountType)
		require.Equal(t, pmodel.LedgerAccountStatusActive, acc.Status)
	}

	// Idempotent: second call should not create new accounts.
	require.NoError(t, svc.ensureSystemAccounts(context.Background()))
	require.Len(t, accounts.created, 4)
}
