package store

import (
	"context"
	"errors"
	"strings"
	"time"

	"github.com/tech/sendico/payments/orchestrator/storage"
	"github.com/tech/sendico/payments/orchestrator/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/bson/primitive"
	"go.uber.org/zap"
)

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

// NewQuotes constructs a Mongo-backed quotes store.
func NewQuotes(logger mlogger.Logger, repo repository.Repository) (*Quotes, error) {
	if repo == nil {
		return nil, merrors.InvalidArgument("quotesStore: repository is nil")
	}

	indexes := []*ri.Definition{
		{
			Keys:   []ri.Key{{Field: "quoteRef", Sort: ri.Asc}},
			Unique: true,
		},
		{
			Keys: []ri.Key{{Field: "organizationRef", Sort: ri.Asc}},
		},
		{
			Keys: []ri.Key{{Field: "expiresAt", Sort: ri.Asc}},
			TTL:  int32Ptr(0),
		},
	}

	for _, def := range indexes {
		if err := repo.CreateIndex(def); err != nil {
			logger.Error("failed to ensure quotes index", zap.Error(err), zap.String("collection", repo.Collection()))
			return nil, err
		}
	}

	return &Quotes{
		logger: logger.Named("quotes"),
		repo:   repo,
	}, nil
}

func (q *Quotes) Create(ctx context.Context, quote *model.PaymentQuoteRecord) error {
	if quote == nil {
		return merrors.InvalidArgument("quotesStore: nil quote")
	}
	quote.QuoteRef = strings.TrimSpace(quote.QuoteRef)
	if quote.QuoteRef == "" {
		return merrors.InvalidArgument("quotesStore: empty quoteRef")
	}
	if quote.OrganizationRef == primitive.NilObjectID {
		return merrors.InvalidArgument("quotesStore: organization_ref is required")
	}
	if quote.IdempotencyKey == "" {
		return merrors.InvalidArgument("quotesStore: idempotency key is required")
	}
	if quote.ExpiresAt.IsZero() {
		return merrors.InvalidArgument("quotesStore: expires_at is required")
	}
	if quote.Intent.Attributes != nil {
		for k, v := range quote.Intent.Attributes {
			quote.Intent.Attributes[k] = strings.TrimSpace(v)
		}
	}
	if len(quote.Intents) > 0 {
		for i := range quote.Intents {
			if quote.Intents[i].Attributes == nil {
				continue
			}
			for k, v := range quote.Intents[i].Attributes {
				quote.Intents[i].Attributes[k] = strings.TrimSpace(v)
			}
		}
	}
	quote.Update()

	filter := repository.OrgFilter(quote.OrganizationRef).And(
		repository.Filter("quoteRef", quote.QuoteRef),
	)

	if err := q.repo.Insert(ctx, quote, filter); err != nil {
		if errors.Is(err, merrors.ErrDataConflict) {
			return storage.ErrDuplicateQuote
		}
		return err
	}
	return nil
}

func (q *Quotes) GetByRef(ctx context.Context, orgRef primitive.ObjectID, quoteRef string) (*model.PaymentQuoteRecord, error) {
	quoteRef = strings.TrimSpace(quoteRef)
	if quoteRef == "" {
		return nil, merrors.InvalidArgument("quotesStore: empty quoteRef")
	}
	if orgRef == primitive.NilObjectID {
		return nil, merrors.InvalidArgument("quotesStore: organization_ref is required")
	}
	entity := &model.PaymentQuoteRecord{}
	query := repository.OrgFilter(orgRef).And(repository.Filter("quoteRef", quoteRef))
	if err := q.repo.FindOneByFilter(ctx, query, entity); err != nil {
		if errors.Is(err, merrors.ErrNoData) {
			return nil, storage.ErrQuoteNotFound
		}
		return nil, err
	}
	if !entity.ExpiresAt.IsZero() && time.Now().After(entity.ExpiresAt) {
		return nil, storage.ErrQuoteNotFound
	}
	return entity, nil
}

func (q *Quotes) GetByIdempotencyKey(ctx context.Context, idempotencyKey string) (*model.PaymentQuoteRecord, error) {
	idempotencyKey = strings.TrimSpace(idempotencyKey)
	if idempotencyKey == "" {
		return nil, merrors.InvalidArgument("quotesStore: empty idempotency key")
	}
	entity := &model.PaymentQuoteRecord{}
	query := repository.Filter("idempotencyKey", idempotencyKey)
	if err := q.repo.FindOneByFilter(ctx, query, entity); err != nil {
		if errors.Is(err, merrors.ErrNoData) {
			return nil, storage.ErrQuoteNotFound
		}
		return nil, err
	}
	if !entity.ExpiresAt.IsZero() && time.Now().After(entity.ExpiresAt) {
		return nil, storage.ErrQuoteNotFound
	}
	return entity, nil
}

var _ storage.QuotesStore = (*Quotes)(nil)

func int32Ptr(v int32) *int32 {
	return &v
}
