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 pairStore struct {
	logger mlogger.Logger
	repo   repository.Repository
}

func NewPair(logger mlogger.Logger, db *mongo.Database) (storage.PairStore, error) {
	repo := repository.CreateMongoRepository(db, model.PairsCollection)
	index := &ri.Definition{
		Keys: []ri.Key{
			{Field: "pair.base", Sort: ri.Asc},
			{Field: "pair.quote", Sort: ri.Asc},
		},
		Unique: true,
	}
	if err := repo.CreateIndex(index); err != nil {
		logger.Error("Failed to ensure pairs index", zap.Error(err))
		return nil, err
	}
	logger.Debug("Pair store initialised", zap.String("collection", model.PairsCollection))

	return &pairStore{
		logger: logger.Named(model.PairsCollection),
		repo:   repo,
	}, nil
}

func (p *pairStore) ListEnabled(ctx context.Context) ([]*model.Pair, error) {
	filter := repository.Query().Filter(repository.Field("isEnabled"), true)

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

func (p *pairStore) Get(ctx context.Context, pair model.CurrencyPair) (*model.Pair, error) {
	if pair.Base == "" || pair.Quote == "" {
		p.logger.Warn("Attempt to fetch pair with empty currency", zap.String("base", pair.Base), zap.String("quote", pair.Quote))
		return nil, merrors.InvalidArgument("pairStore: incomplete pair")
	}
	result := &model.Pair{}
	query := repository.Query().
		Filter(repository.Field("pair").Dot("base"), pair.Base).
		Filter(repository.Field("pair").Dot("quote"), pair.Quote)
	if err := p.repo.FindOneByFilter(ctx, query, result); err != nil {
		if errors.Is(err, merrors.ErrNoData) {
			p.logger.Debug("Pair not found", zap.String("base", pair.Base), zap.String("quote", pair.Quote))
		}
		return nil, err
	}
	p.logger.Debug("Pair loaded", zap.String("base", pair.Base), zap.String("quote", pair.Quote))
	return result, nil
}

func (p *pairStore) Upsert(ctx context.Context, pair *model.Pair) error {
	if pair == nil {
		p.logger.Warn("Attempt to upsert nil pair")
		return merrors.InvalidArgument("pairStore: nil pair")
	}
	if pair.Pair.Base == "" || pair.Pair.Quote == "" {
		p.logger.Warn("Attempt to upsert pair with empty currency", zap.String("base", pair.Pair.Base), zap.String("quote", pair.Pair.Quote))
		return merrors.InvalidArgument("pairStore: incomplete pair")
	}

	existing := &model.Pair{}
	query := repository.Query().
		Filter(repository.Field("pair").Dot("base"), pair.Pair.Base).
		Filter(repository.Field("pair").Dot("quote"), pair.Pair.Quote)
	err := p.repo.FindOneByFilter(ctx, query, existing)
	if err != nil {
		if errors.Is(err, merrors.ErrNoData) {
			p.logger.Debug("Inserting new pair", zap.String("base", pair.Pair.Base), zap.String("quote", pair.Pair.Quote))
			return p.repo.Insert(ctx, pair, query)
		}
		p.logger.Warn("Failed to fetch pair", zap.Error(err), zap.String("base", pair.Pair.Base), zap.String("quote", pair.Pair.Quote))
		return err
	}

	if existing.GetID() != nil {
		pair.SetID(*existing.GetID())
	}
	p.logger.Debug("Updating pair", zap.String("base", pair.Pair.Base), zap.String("quote", pair.Pair.Quote))
	return p.repo.Update(ctx, pair)
}
