package store

import (
	"context"
	"errors"

	"github.com/tech/sendico/fx/storage"
	"github.com/tech/sendico/fx/storage/model"
	"github.com/tech/sendico/pkg/db/repository"
	ri "github.com/tech/sendico/pkg/db/repository/index"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.uber.org/zap"
)

type currencyStore struct {
	logger mlogger.Logger
	repo   repository.Repository
}

func NewCurrency(logger mlogger.Logger, db *mongo.Database) (storage.CurrencyStore, error) {
	repo := repository.CreateMongoRepository(db, model.CurrenciesCollection)

	index := &ri.Definition{
		Keys: []ri.Key{
			{Field: "code", Sort: ri.Asc},
		},
		Unique: true,
	}
	if err := repo.CreateIndex(index); err != nil {
		logger.Error("Failed to ensure currencies index", zap.Error(err))
		return nil, err
	}
	childLogger := logger.Named(model.CurrenciesCollection)
	childLogger.Debug("Currency store initialised", zap.String("collection", model.CurrenciesCollection))

	return &currencyStore{
		logger: childLogger,
		repo:   repo,
	}, nil
}

func (c *currencyStore) Get(ctx context.Context, code string) (*model.Currency, error) {
	if code == "" {
		c.logger.Warn("Attempt to fetch currency with empty code")
		return nil, merrors.InvalidArgument("currencyStore: empty code")
	}
	result := &model.Currency{}
	if err := c.repo.FindOneByFilter(ctx, repository.Filter("code", code), result); err != nil {
		if errors.Is(err, merrors.ErrNoData) {
			c.logger.Debug("Currency not found", zap.String("code", code))
		}
		return nil, err
	}
	c.logger.Debug("Currency loaded", zap.String("code", code))
	return result, nil
}

func (c *currencyStore) List(ctx context.Context, codes ...string) ([]*model.Currency, error) {
	query := repository.Query()
	if len(codes) > 0 {
		values := make([]any, len(codes))
		for i, code := range codes {
			values[i] = code
		}
		query = query.In(repository.Field("code"), values...)
	}

	currencies := make([]*model.Currency, 0)
	err := c.repo.FindManyByFilter(ctx, query, func(cur *mongo.Cursor) error {
		doc := &model.Currency{}
		if err := cur.Decode(doc); err != nil {
			return err
		}
		currencies = append(currencies, doc)
		return nil
	})
	if err != nil {
		c.logger.Warn("Failed to list currencies", zap.Error(err))
		return nil, err
	}
	c.logger.Debug("Listed currencies", zap.Int("count", len(currencies)))
	return currencies, nil
}

func (c *currencyStore) Upsert(ctx context.Context, currency *model.Currency) error {
	if currency == nil {
		c.logger.Warn("Attempt to upsert nil currency")
		return merrors.InvalidArgument("currencyStore: nil currency")
	}
	if currency.Code == "" {
		c.logger.Warn("Attempt to upsert currency with empty code")
		return merrors.InvalidArgument("currencyStore: empty code")
	}

	existing := &model.Currency{}
	filter := repository.Filter("code", currency.Code)
	if err := c.repo.FindOneByFilter(ctx, filter, existing); err != nil {
		if errors.Is(err, merrors.ErrNoData) {
			c.logger.Debug("Inserting new currency", zap.String("code", currency.Code))
			return c.repo.Insert(ctx, currency, filter)
		}
		c.logger.Warn("Failed to fetch currency", zap.Error(err), zap.String("code", currency.Code))
		return err
	}

	if existing.GetID() != nil {
		currency.SetID(*existing.GetID())
	}
	c.logger.Debug("Updating currency", zap.String("code", currency.Code))
	return c.repo.Update(ctx, currency)
}
