package repository

import (
	"context"
	"fmt"
	"log"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.uber.org/zap"
	mongoWrapper "penahub.gitlab.yandexcloud.net/backend/penahub_common/mongo"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/fields"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
)

type CurrencyRepositoryDeps struct {
	Logger  *zap.Logger
	MongoDB *mongo.Collection
}

type CurrencyRepository struct {
	logger  *zap.Logger
	mongoDB *mongo.Collection
}

func NewCurrencyRepository(deps CurrencyRepositoryDeps) *CurrencyRepository {
	if deps.Logger == nil {
		log.Panicln("logger is nil on <NewCurrencyRepository>")
	}

	if deps.MongoDB == nil {
		log.Panicln("mongodb is nil on <NewCurrencyRepository>")
	}

	return &CurrencyRepository{
		mongoDB: deps.MongoDB,
		logger:  deps.Logger,
	}
}

func (receiver *CurrencyRepository) FindCurrenciesList(ctx context.Context, name string) (*models.CurrencyList, errors.Error) {
	filter := bson.M{
		fields.Currency.Name:      name,
		fields.Currency.IsDeleted: false,
	}

	currencyList, err := mongoWrapper.FindOne[models.CurrencyList](ctx, &mongoWrapper.RequestSettings{
		Driver: receiver.mongoDB,
		Filter: filter,
	})
	if err != nil {
		receiver.logger.Error("failed to find list on <FindCurrenciesList> of <CurrencyRepository>",
			zap.String("name", name),
			zap.Error(err),
		)

		findError := errors.New(
			fmt.Errorf("failed to find list with <%s> on <FindCurrenciesList> of <CurrencyRepository>: %w", name, err),
			errors.ErrInternalError,
		)

		if err == mongo.ErrNoDocuments {
			findError.SetType(errors.ErrNotFound)
		}

		return nil, findError
	}

	return currencyList, nil
}

func (receiver *CurrencyRepository) ReplaceCurrencies(ctx context.Context, list *models.CurrencyList) (*models.CurrencyList, errors.Error) {
	currencyList := models.CurrencyList{}

	filter := bson.M{
		fields.Currency.Name:      list.Name,
		fields.Currency.IsDeleted: false,
	}

	update := bson.M{
		"$set": bson.M{
			fields.Currency.UpdatedAt:  time.Now(),
			fields.Currency.Currencies: list.Currencies,
		},
	}

	if err := receiver.mongoDB.FindOneAndUpdate(ctx, filter, update).Decode(&currencyList); err != nil {
		receiver.logger.Error("failed to insert currencies on <InsertCurrenciesToList> of <CurrencyRepository>",
			zap.String("name", list.Name),
			zap.Error(err),
		)

		removeErr := errors.New(
			fmt.Errorf("failed to insert currencies <%s> on <InsertCurrenciesToList> of <CurrencyRepository>: %w", list.Name, err),
			errors.ErrInternalError,
		)

		if err == mongo.ErrNoDocuments {
			removeErr.SetType(errors.ErrNotFound)
		}

		return nil, removeErr
	}

	return &currencyList, nil
}

func (receiver *CurrencyRepository) Insert(ctx context.Context, list *models.CurrencyList) (*models.CurrencyList, errors.Error) {
	result, err := receiver.mongoDB.InsertOne(ctx, list.Sanitize())
	if err != nil {
		receiver.logger.Error("failed to insert currency list on <Insert> of <CurrencyRepository>",
			zap.Any("list", list),
			zap.Error(err),
		)

		return nil, errors.New(
			fmt.Errorf("failed to insert currency list on <Insert> of <CurrencyRepository>: %w", err),
			errors.ErrInternalError,
		)
	}

	insertedID := result.InsertedID.(primitive.ObjectID).Hex()
	list.ID = insertedID

	return list, nil
}
