package swagger

import (
	"fmt"
	"github.com/gofiber/fiber/v2"
	"math"
	"net/http"
	"net/url"
	"os"
	codeword_rpc "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/codeword"
	"sync"
	"time"

	"github.com/labstack/echo/v4"
	"go.mongodb.org/mongo-driver/mongo"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/timestamppb"
	qutils "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/utils"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/broker/tariff"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/client"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/repository"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/discount"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/service/history"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils/transfer"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/echotools"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/validate"
	tb "gopkg.in/tucnak/telebot.v2"
)

const defaultCurrency = "RUB" // TODO move

type API2 struct {
	logger   *zap.Logger
	history  repository.HistoryRepository
	account  repository.AccountRepository
	currency repository.CurrencyRepository
	producer *tariff.Producer
	consumer *tariff.Consumer
	clients  clients
	grpc     models.ConfigurationGRPC
	encrypt  *qutils.Encrypt
	notifier *tb.Bot
	notifyChannel int64
	adminURL string
}

type clients struct {
	auth     *client.AuthClient
	hubadmin *client.HubadminClient
	currency *client.CurrencyClient
	discount *client.DiscountClient
	payment  *client.PaymentClient
	verify   *client.VerificationClient
	template *client.TemplateClient
	mail     *client.MailClient
	codeword *client.CodewordClient
}

var _ ServerInterface = (*API2)(nil)

func NewAPI2(logger *zap.Logger, db *mongo.Database, config *models.Config, consumer *tariff.Consumer, producer *tariff.Producer, encrypt *qutils.Encrypt, notfier *tb.Bot, channel int64, adminURL string) API2 {
	return API2{
		logger:   logger,
		history:  repository.NewHistoryRepository2(logger, db.Collection("histories")),
		currency: repository.NewCurrencyRepository2(logger, db.Collection("currency_lists")),
		account:  repository.NewAccountRepository2(logger, db.Collection("accounts")),
		consumer: consumer,
		producer: producer,
		encrypt:  encrypt,
		notifier: notfier,
		grpc:     config.GRPC,
		notifyChannel: channel,
		adminURL: adminURL,
		clients: clients{
			auth:     client.NewAuthClient(client.AuthClientDeps{Logger: logger, URLs: &config.Service.AuthMicroservice.URL}),
			hubadmin: client.NewHubadminClient(client.HubadminClientDeps{Logger: logger, URLs: &config.Service.HubadminMicroservice.URL}),
			currency: client.NewCurrencyClient(client.CurrencyClientDeps{Logger: logger, URLs: &config.Service.CurrencyMicroservice.URL}),
			discount: client.NewDiscountClient(client.DiscountClientDeps{Logger: logger, DiscountServiceHost: config.Service.DiscountMicroservice.HostGRPC}),
			payment:  client.NewPaymentClient(client.PaymentClientDeps{Logger: logger, PaymentServiceHost: config.Service.PaymentMicroservice.HostGRPC}),
			verify:   client.NewVerificationClient(client.VerificationClientDeps{Logger: logger, URLs: &config.Service.VerificationMicroservice.URL}),
			template: client.NewTemplateClient(client.TemplateClientDeps{Logger: logger, URLs: &config.Service.TemplategenMicroserviceURL.URL}),
			mail: client.NewMailClient(client.MailClientDeps{Logger: logger,
				ApiUrl: config.Service.Mail.ApiUrl,
				Sender: config.Service.Mail.Sender,
				Auth: &models.PlainAuth{
					Identity: config.Service.Mail.Auth.Identity,
					Username: config.Service.Mail.Auth.Username,
					Password: config.Service.Mail.Auth.Password,
				},
				ApiKey:      config.Service.Mail.ApiKey,
				FiberClient: fiber.AcquireClient(),
				MailAddress: config.Service.Mail.MailAddress,
			}),
			codeword: client.NewCodewordClient(client.CodewordClientDeps{Logger: logger, CodewordServiceHost: config.Service.CodewordMicroservice.HostGRPC}),
		},
	}
}

func (api *API2) error(ctx echo.Context, status int, message string, rest ...any) error {
	if len(rest) > 0 {
		message = fmt.Sprintf(message, rest...)
	}
	api.logger.Error(message)
	return ctx.JSON(status, models.ResponseErrorHTTP{
		StatusCode: status,
		Message:    message,
	})
}

func (api *API2) errorOld(ctx echo.Context, err errors.Error) error {
	api.logger.Error("error:", zap.Error(err))
	return errors.HTTP(ctx, err)
}

func (api *API2) noauth(ctx echo.Context) error {
	return api.error(ctx, http.StatusUnauthorized, "failed to get jwt payload")
}

// Health

func (api *API2) GetHealth(ctx echo.Context) error {
	return ctx.String(http.StatusOK, "OK")
}

// Account

func (api *API2) DeleteAccount(ctx echo.Context) error {
	userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	account, err := api.account.Remove(ctx.Request().Context(), userID)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, account)
}

func (api *API2) ChangeAccount(ctx echo.Context) error {
	userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	request, bindErr := echotools.Bind[models.Name](ctx)
	if bindErr != nil {
		return api.error(ctx, http.StatusBadRequest, "failed to bind json: %w", bindErr)
	}

	account, err := api.account.UpdateName(ctx.Request().Context(), userID, request)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, account)
}

func (api *API2) SetAccountVerificationStatus(ctx echo.Context, userID string) error {
	request, bindErr := echotools.Bind[models.SetAccountStatus](ctx)
	if bindErr != nil {
		return api.error(ctx, http.StatusBadRequest, "failed to bind json: %w", bindErr)
	}

	account, err := api.account.SetStatus(ctx.Request().Context(), userID, request.Status)
	if err != nil {
		api.logger.Error("failed to set status on <SetVerificationStatus> of <AccountService>", zap.Error(err))
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, account)
}

func (api *API2) GetAccount(ctx echo.Context) error {
	userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, account)
}

func (api *API2) AddAccount(ctx echo.Context) error {
	userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	var quizFrom, quizUser string
	cookie, er := ctx.Request().Cookie("quizFrom")
	if er == nil {
		quizFrom = cookie.Value
	}

	cookie, er = ctx.Request().Cookie("quizUser")
	if er == nil {
		quizUser, er = url.QueryUnescape(cookie.Value)
		if er == nil {
			quizUser, er = api.encrypt.DecryptStr([]byte(quizUser))
		}
	}

	account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
	if err != nil && err.Type() != errors.ErrNotFound {
		return api.errorOld(ctx, err)
	}

	if account != nil {
		return api.error(ctx, http.StatusBadRequest, "account exists")
	}

	user, err := api.clients.auth.GetUser(ctx.Request().Context(), userID)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	account, err = api.account.Insert(ctx.Request().Context(), &models.Account{
		UserID: user.ID, Wallet: models.Wallet{Currency: defaultCurrency}, From: quizFrom, Partner: quizUser})
	if err != nil {
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, account)
}

func (api *API2) DeleteDirectAccount(ctx echo.Context, userID string) error {
	account, err := api.account.Remove(ctx.Request().Context(), userID)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, account)
}

func (api *API2) GetDirectAccount(ctx echo.Context, userID string) error {
	account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, account)
}

func (api *API2) PaginationAccounts(ctx echo.Context, params PaginationAccountsParams) error {
	if params.Page == nil || params.Limit == nil {
		return api.error(ctx, http.StatusInternalServerError, "default values missing for PaginationAccounts")
	}

	page := int64(math.Max(float64(*params.Page), 1))
	limit := int64(math.Max(float64(*params.Limit), 1))
	limit = int64(math.Min(float64(limit), float64(models.DefaultLimit)))

	count, err := api.account.CountAll(ctx.Request().Context())
	if err != nil {
		return api.errorOld(ctx, err)
	}

	if count == 0 {
		response := models.PaginationResponse[models.Account]{TotalPages: 0, Records: []models.Account{}}
		return ctx.JSON(http.StatusOK, response)
	}

	totalPages := int64(math.Ceil(float64(count) / float64(limit)))

	accounts, err := api.account.FindMany(ctx.Request().Context(), page, limit)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	response := models.PaginationResponse[models.Account]{
		TotalPages: totalPages,
		Records:    accounts,
	}

	return ctx.JSON(http.StatusOK, response)
}

// Cart

func (api *API2) RemoveFromCart(ctx echo.Context, params RemoveFromCartParams) error {
	userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	if validate.IsStringEmpty(params.Id) {
		return api.error(ctx, http.StatusBadRequest, "empty item id")
	}

	cartItems, err := api.account.RemoveItemFromCart(ctx.Request().Context(), userID, params.Id)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, cartItems)
}

func (api *API2) Add2cart(ctx echo.Context, params Add2cartParams) error {
	userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	token, ok := ctx.Get(models.AuthJWTDecodedAccessTokenKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	if validate.IsStringEmpty(params.Id) {
		return api.error(ctx, http.StatusBadRequest, "empty item id")
	}

	tariffID := params.Id

	tariff, err := api.clients.hubadmin.GetTariff(ctx.Request().Context(), token, tariffID)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	if tariff == nil {
		return api.error(ctx, http.StatusNotFound, "tariff not found")
	}

	cartItems, err := api.account.AddItemToCart(ctx.Request().Context(), userID, tariffID)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, cartItems)
}

func (api *API2) PayCart(ctx echo.Context) error {
	userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	accessToken, ok := ctx.Get(models.AuthJWTDecodedAccessTokenKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	api.logger.Info("account for pay", zap.Any("acc", account))

	tariffs, err := api.clients.hubadmin.GetTariffs(ctx.Request().Context(), accessToken, account.Cart)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	api.logger.Info("tariffs for pay", zap.Any("acc", tariffs))

	tariffsAmount := utils.CalculateCartPurchasesAmount(tariffs)

	discountResponse, err := api.clients.discount.Apply(ctx.Request().Context(), &discount.ApplyDiscountRequest{
		UserInformation: &discount.UserInformation{
			ID:                  account.UserID,
			Type:                string(account.Status),
			PurchasesAmount:     uint64(account.Wallet.Spent),
			CartPurchasesAmount: tariffsAmount,
		},
		Products: transfer.TariffsToProductInformations(tariffs),
		Date:     timestamppb.New(time.Now()),
	})
	if err != nil {
		return api.errorOld(ctx, err)
	}

	api.logger.Info("discountResponse for pay", zap.Any("acc", discount.ApplyDiscountRequest{
		UserInformation: &discount.UserInformation{
			ID:                  account.UserID,
			Type:                string(account.Status),
			PurchasesAmount:     uint64(account.Wallet.Spent),
			CartPurchasesAmount: tariffsAmount,
		},
		Products: transfer.TariffsToProductInformations(tariffs),
		Date:     timestamppb.New(time.Now()),
	}))

	if account.Wallet.Money < int64(discountResponse.Price) {
		return api.error(ctx, http.StatusPaymentRequired, "insufficient funds: %d", int64(discountResponse.Price)-account.Wallet.Money)
	}

	for _, applied := range discountResponse.AppliedDiscounts {
		if applied.Condition.User != nil && *applied.Condition.User != "" {
			_, err := api.clients.discount.DeleteDiscount(ctx.Request().Context(), &discount.GetDiscountByIDRequest{
				ID: applied.ID,
			})

			if err != nil {
				return api.error(ctx, http.StatusInternalServerError, "failed delete discount by id:%s", applied.ID)
			}
		}
	}

	// WithdrawAccountWalletMoney

	request := models.WithdrawAccountWallet{
		Money:   int64(discountResponse.Price),
		Account: account,
	}

	if validate.IsStringEmpty(request.Account.Wallet.Currency) {
		request.Account.Wallet.Currency = models.InternalCurrencyKey
	}

	var updatedAccount *models.Account

	if request.Account.Wallet.Currency == models.InternalCurrencyKey {
		accountx, err := api.account.ChangeWallet(ctx.Request().Context(), request.Account.UserID, &models.Wallet{
			Cash:            request.Account.Wallet.Cash - request.Money,
			Money:           request.Account.Wallet.Money - request.Money,
			Spent:           request.Account.Wallet.Spent + request.Money,
			PurchasesAmount: request.Account.Wallet.PurchasesAmount,
			Currency:        request.Account.Wallet.Currency,
		})
		if err != nil {
			return api.errorOld(ctx, err)
		}
		updatedAccount = accountx
	} else {
		cash, err := api.clients.currency.Translate(ctx.Request().Context(), &models.TranslateCurrency{
			Money: request.Money,
			From:  models.InternalCurrencyKey,
			To:    request.Account.Wallet.Currency,
		})
		if err != nil {
			return api.errorOld(ctx, err)
		}

		accountx, err := api.account.ChangeWallet(ctx.Request().Context(), request.Account.UserID, &models.Wallet{
			Cash:            request.Account.Wallet.Cash - cash,
			Money:           request.Account.Wallet.Money - request.Money,
			Spent:           request.Account.Wallet.Spent + request.Money,
			PurchasesAmount: request.Account.Wallet.PurchasesAmount,
			Currency:        request.Account.Wallet.Currency,
		})
		if err != nil {
			return api.errorOld(ctx, err)
		}

		updatedAccount = accountx
	}

	if _, err := api.history.Insert(ctx.Request().Context(), &models.History{
		Key:     models.CustomerHistoryKeyPayCart,
		UserID:  account.UserID,
		Comment: "Успешная оплата корзины",
		RawDetails: models.RawDetails{
			Tariffs: tariffs,
			Price:   int64(discountResponse.Price),
		},
	}); err != nil {
		return api.errorOld(ctx, err)
	}

	// TODO: обработать ошибки при отправке сообщений

	sendErrors := make([]errors.Error, 0)
	waitGroup := sync.WaitGroup{}
	mutex := sync.Mutex{}

	for _, tariff := range tariffs {
		waitGroup.Add(1)

		go func(currentTariff models.Tariff) {
			defer waitGroup.Done()

			if err := api.producer.Send(ctx.Request().Context(), userID, &currentTariff); err != nil {
				mutex.Lock()
				defer mutex.Unlock()
				sendErrors = append(sendErrors, err)
			}
		}(tariff)
	}

	waitGroup.Wait()

	if len(sendErrors) > 0 {
		for _, err := range sendErrors {
			api.logger.Error("failed to send tariffs to broker on <Pay> of <CartService>", zap.Error(err))
		}
		return api.error(ctx, http.StatusInternalServerError, "failed to send tariffs to broker")
	}

	if _, err := api.account.ClearCart(ctx.Request().Context(), account.UserID); err != nil {
		return api.errorOld(ctx, err)
	}
	go func () {
		var privilegesdesc string
		for _, tariff := range tariffs {
			for _, privilege := range tariff.Privileges {
				privilegesdesc += privilege.Name + " " + fmt.Sprint(privilege.Amount) + "\n"
			}
		}
	if _, err := api.notifier.Send(tb.ChatID(api.notifyChannel), fmt.Sprintf(`Куплена корзина за %.2f, пользователем %s
	куплены:
	%s`,float64(discountResponse.Price/100), api.adminURL + "/users/" + account.UserID,privilegesdesc)); err != nil {
		fmt.Println("TBOER", err)
	}
}()

	updatedAccount.Cart = []string{}

	return ctx.JSON(http.StatusOK, updatedAccount)
}

// Currency

func (api *API2) GetCurrencies(ctx echo.Context) error {
	currencyList, err := api.currency.FindCurrenciesList(ctx.Request().Context(), models.DefaultCurrencyListName)
	if err != nil && err.Type() != errors.ErrNotFound {
		return api.errorOld(ctx, err)
	}

	if err != nil && err.Type() == errors.ErrNotFound {
		return ctx.JSON(http.StatusOK, []string{})
	}

	return ctx.JSON(http.StatusOK, currencyList)
}

func (api *API2) UpdateCurrencies(ctx echo.Context) error {
	currenciesPtr, bindErr := echotools.Bind[[]string](ctx)
	if bindErr != nil {
		return api.error(ctx, http.StatusBadRequest, "faild to bind currencies")
	}

	currencies := *currenciesPtr

	currencyList, err := api.currency.ReplaceCurrencies(ctx.Request().Context(), &models.CurrencyList{
		Name:       models.DefaultCurrencyListName,
		Currencies: currencies,
	})
	if err != nil && err.Type() != errors.ErrNotFound {
		return api.errorOld(ctx, err)
	}

	if err != nil && err.Type() == errors.ErrNotFound {
		newCurrencyList, err := api.currency.Insert(ctx.Request().Context(), &models.CurrencyList{
			Name:       models.DefaultCurrencyListName,
			Currencies: currencies,
		})
		if err != nil && err.Type() != errors.ErrNotFound {
			return api.errorOld(ctx, err)
		}
		return ctx.JSON(http.StatusOK, newCurrencyList.Currencies)
	}

	return ctx.JSON(http.StatusOK, currencyList.Currencies)
}

// History

func (api *API2) GetHistory(ctx echo.Context, params GetHistoryParams) error {
	var userID string

	if params.AccountID != nil && *params.AccountID != "" {
		userID = *params.AccountID
	} else {
		userID, _ = ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	}

	dto := &history.GetHistories{
		UserID: userID,
		Type:   params.Type,
		Pagination: &models.Pagination{
			Page:  int64(*params.Page),
			Limit: int64(*params.Limit),
		},
	}

	count, err := api.history.CountAll(ctx.Request().Context(), dto)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	if count == 0 {
		returnHistories := models.PaginationResponse[models.History]{TotalPages: 0, Records: []models.History{}}
		return ctx.JSON(http.StatusOK, returnHistories)
	}

	totalPages := int64(math.Ceil(float64(count) / float64(dto.Pagination.Limit)))

	histories, err := api.history.FindMany(ctx.Request().Context(), dto)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	returnHistories := models.PaginationResponse[models.History]{
		TotalPages: totalPages,
		Records:    histories,
	}

	return ctx.JSON(http.StatusOK, returnHistories)
}

// Wallet

func (api *API2) RequestMoney(ctx echo.Context) error {
	userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	//token := ctx.Request().Header.Get("Authorization")

	request, bindErr := echotools.Bind[models.GetPaymentLinkBody](ctx)
	if bindErr != nil {
		return api.error(ctx, http.StatusBadRequest, "faild to bind payment link")
	}

	if err := utils.ValidateGetPaymentLinkBody(request); err != nil {
		return api.errorOld(ctx, err)
	}

	// account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
	// if err != nil {
	// 	return api.error(ctx, http.StatusBadRequest, "failed to find account")
	// }

	// var fullName string
	// if account.Status == models.AccountStatusNo || account.Name.Orgname == "" {
	// 	fullName = fmt.Sprintf("%s %s %s", account.Name.Middlename, account.Name.FirstName, account.Name.Secondname)
	// } else {
	// 	fullName = account.Name.Orgname
	// }

	user, err := api.clients.auth.GetUser(ctx.Request().Context(), userID)
	if err != nil {
		return api.error(ctx, http.StatusInternalServerError, "error fetch user account in auth service")
	}

	// verifyData, err := api.clients.verify.GetVerification(ctx.Request().Context(), token, userID)
	// if err != nil {
	// 	return api.error(ctx, http.StatusInternalServerError, "error fetch user verify in verify service")
	// }
	//
	link, err := api.GetPaymentLink(ctx.Request().Context(), &models.GetPaymentLinkRequest{
		Body:     request,
		UserID:   userID,
		ClientIP: ctx.RealIP(),
		Customer: models.Customer{
		//	FullName: fullName,
			Email:    user.Login,
			Phone:    user.PhoneNumber,
		//	INN:      verifyData.TaxNumber,
		},
		Items: []models.Item{{
			Description: "Пополнение пользовательского баланса платформы Pena Hub",
			Measure: "another",
			Quantity: "1",
			Currency: "RUB",
			Money: fmt.Sprintf("%.2f",float64(request.Amount)/100),
		}},
	})
	if err != nil {
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, &models.GetPaymentLinkResponse{Link: link})
}

func (api *API2) ChangeCurrency(ctx echo.Context) error {
	userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	request, bindErr := echotools.Bind[models.ChangeCurrency](ctx)
	if bindErr != nil {
		return api.error(ctx, http.StatusBadRequest, "faild to bind currency")
	}

	if validate.IsStringEmpty(request.Currency) {
		return api.error(ctx, http.StatusBadRequest, "empty currency")
	}

	currency := request.Currency
	account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	cash, err := api.clients.currency.Translate(ctx.Request().Context(), &models.TranslateCurrency{
		Money: account.Wallet.Cash,
		From:  account.Wallet.Currency,
		To:    currency,
	})
	if err != nil {
		return api.errorOld(ctx, err)
	}

	updatedAccount, err := api.account.ChangeWallet(ctx.Request().Context(), account.UserID, &models.Wallet{
		Cash:     cash,
		Currency: currency,
		Money:    account.Wallet.Money,
	})
	if err != nil {
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, updatedAccount)
}

func (api *API2) CalculateLTV(ctx echo.Context) error {
	var req CalculateLTVJSONBody

	if err := ctx.Bind(&req); err != nil {
		api.logger.Error("failed to bind request", zap.Error(err))
		return api.error(ctx, http.StatusBadRequest, "failed to bind request")
	}

	if req.From > req.To && req.To != 0 {
		api.logger.Error("From timestamp must be less than To timestamp unless To is 0")
		return api.error(ctx, http.StatusBadRequest, "From timestamp must be less than To timestamp unless To is 0")
	}

	ltv, err := api.history.CalculateCustomerLTV(ctx.Request().Context(), req.From, req.To)
	if err != nil {
		api.logger.Error("failed to calculate LTV", zap.Error(err))
		return api.errorOld(ctx, err)
	}

	response := struct {
		LTV int64 `json:"LTV"`
	}{
		LTV: ltv,
	}

	return ctx.JSON(http.StatusOK, response)
}

func (api *API2) GetRecentTariffs(ctx echo.Context) error {
	userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	if !ok {
		api.logger.Error("failed to convert jwt payload to string on <GetRecentTariffs> of <API2>")
		return api.error(ctx, http.StatusBadRequest, "failed to convert jwt payload to string")
	}

	if userID == "" {
		api.logger.Error("user id is missing in <GetRecentTariffs> of <API2>")
		return api.error(ctx, http.StatusBadRequest, "user id is missing")
	}

	tariffs, err := api.history.GetRecentTariffs(ctx.Request().Context(), userID)
	if err != nil {
		api.logger.Error("failed to get recent tariffs on <GetRecentTariffs> of <API2>",
			zap.String("userId", userID),
			zap.Error(err),
		)
		return api.errorOld(ctx, err)
	}

	return ctx.JSON(http.StatusOK, tariffs)
}

func (api *API2) SendReport(ctx echo.Context) error {
	var req SendReportJSONBody
	if err := ctx.Bind(&req); err != nil {
		api.logger.Error("failed to bind request", zap.Error(err))
		return api.error(ctx, http.StatusBadRequest, "failed to bind request")
	}

	if req.Id == "" {
		api.logger.Error("history id is missing in <GetHistoryById> of <HistoryService>")
		return api.error(ctx, http.StatusBadRequest, "history id is missing")
	}

	tariffs, err := api.history.GetHistoryByID(ctx.Request().Context(), req.Id)
	if err != nil {
		api.logger.Error(
			"failed to get history by id in <GetHistoryById> of <HistoryService>",
			zap.String("historyID", req.Id),
			zap.Error(err),
		)
		return api.errorOld(ctx, err)
	}

	if tariffs.Key != models.CustomerHistoryKeyPayCart {
		api.logger.Error(
			"invalid history record key",
			zap.String("historyID", req.Id),
			zap.Error(err),
		)
		return api.error(ctx, http.StatusBadRequest, "invalid history record key")
	}

	historyMap, err := api.history.GetDocNumber(ctx.Request().Context(), tariffs.UserID)
	if err != nil {
		api.logger.Error(
			"failed to get history of sorting by date created in <GetDocNumber> of <HistoryService>",
			zap.String("historyID", req.Id),
			zap.Error(err),
		)
		return api.errorOld(ctx, err)
	}
	token := ctx.Request().Header.Get("Authorization")
	fmt.Println("HEADERS", ctx.Request().Header)

	verifuser, err := api.clients.verify.GetVerification(ctx.Request().Context(), token, tariffs.UserID)
	if err != nil {
		api.logger.Error("failed to get user verification on <GetHistoryById> of <HistoryService>",
			zap.Error(err),
			zap.String("userID", tariffs.UserID),
		)
		return api.errorOld(ctx, err)
	}
	if !verifuser.Accepted {
		api.logger.Error(
			"verification not accepted",
			zap.String("userID", tariffs.UserID),
			zap.Error(err),
		)
		return api.error(ctx, http.StatusBadRequest, "verification not accepted")
	}

	authuser, err := api.clients.auth.GetUser(ctx.Request().Context(), tariffs.UserID)
	if err != nil {
		api.logger.Error("failed to get user on <GetHistoryById> of <HistoryService>",
			zap.Error(err),
			zap.String("userID", tariffs.UserID),
		)
		return api.errorOld(ctx, err)
	}

	fileContents, readerr := os.ReadFile("./report.docx")
	if readerr != nil {
		return api.error(ctx, http.StatusInternalServerError, "failed to read file")
	}

	for _, tariff := range tariffs.RawDetails.Tariffs {
		totalAmount := uint64(0)
		for _, privilege := range tariff.Privileges {
			totalAmount += privilege.Amount
		}
		data := models.RespGeneratorService{
			DocNumber: historyMap[req.Id] + 1,
			Date:      time.Now().Format("2006-01-02"),
			OrgTaxNum: verifuser.TaxNumber,
			OrgName:   models.Name{Orgname: "Orgname"},
			Name:      tariff.Name,
			Amount:    totalAmount,
			Price:     tariffs.RawDetails.Price,
			Sum:       tariffs.RawDetails.Price,
		}
		err = api.clients.template.SendData(ctx.Request().Context(), data, fileContents, authuser.Email)
		if err != nil {
			api.logger.Error("failed to send report to user on <GetHistoryById> of <HistoryService>",
				zap.Error(err),
				zap.String("userID", tariffs.UserID),
			)
			return api.errorOld(ctx, err)
		}
	}

	return ctx.NoContent(http.StatusOK)
}

func (api *API2) PostWalletRspay(ctx echo.Context) error {
	userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
	if !ok {
		return api.noauth(ctx)
	}

	var req PostWalletRspayJSONBody
	if err := ctx.Bind(&req); err != nil {
		api.logger.Error("failed to bind request", zap.Error(err))
		return api.error(ctx, http.StatusBadRequest, "failed to bind request")
	}

	user, err := api.account.FindByUserID(ctx.Request().Context(), userID)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	if user.Status != models.AccountStatusNko && user.Status != models.AccountStatusOrg {
		return api.error(ctx, http.StatusForbidden, "not allowed for non organizations")
	}
	token := ctx.Request().Header.Get("Authorization")
	fmt.Println("HEADERS", ctx.Request().Header)

	verification, err := api.clients.verify.GetVerification(ctx.Request().Context(), token, userID)
	if err == errors.ErrNotFound {
		return api.error(ctx, http.StatusForbidden, "no verification data found")
	}

	if (user.Status == models.AccountStatusOrg && len(verification.Files) != 3) ||
		(user.Status == models.AccountStatusNko && len(verification.Files) != 4) {
		return api.error(ctx, http.StatusForbidden, "not enough verification files")
	}

	authData, err := api.clients.auth.GetUser(ctx.Request().Context(), userID)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	err = api.clients.mail.SendMessage(authData.Login, verification, *req.Money)
	if err != nil {
		return api.errorOld(ctx, err)
	}

	return ctx.NoContent(http.StatusOK)
}

type QuizLogoStat2 struct {
	Count int64           `json:"count,omitempty"`
	Items map[string]Item `json:"items,omitempty"`
}

type Item struct {
	Money  int64             `json:"money,omitempty"`
	Quizes map[string][2]int `json:"quizes,omitempty"`
	Regs   int               `json:"regs,omitempty"`
}

func (api *API2) QuizLogoStat(ctx echo.Context) error {
	var req QuizLogoStatJSONRequestBody
	if err := ctx.Bind(&req); err != nil {
		api.logger.Error("failed to bind request", zap.Error(err))
		return api.error(ctx, http.StatusBadRequest, "failed to bind request")
	}

	result, err := api.account.QuizLogoStat(ctx.Request().Context(), repository.QuizLogoStatDeps{
		Page:  req.Page,
		Limit: req.Limit,
		From:  req.From,
		To:    req.To,
	})
	if err != nil {
		return api.error(ctx, http.StatusInternalServerError, fmt.Sprint("failed getting quiz logo stat", err.Error()))
	}

	return ctx.JSON(http.StatusOK, result)
}

func (api *API2) PromocodeLTV(ctx echo.Context) error {
	var req PromocodeLTVJSONRequestBody
	if err := ctx.Bind(&req); err != nil {
		api.logger.Error("failed to bind request", zap.Error(err))
		return api.error(ctx, http.StatusBadRequest, "failed to bind request")
	}

	// получаем мапу вида [promoID] = []{userid,timeActivate}
	// отдаются только первые использованые на аккаунте промокоды, соответсвенно подсчет идет сугубо по ним
	// если в запросе время различается с временем активации - если меньше, то учитывается только после применения
	// если больше, то учитывается только с начала переданного from
	codewordData, err := api.clients.codeword.GetAllPromoActivations(ctx.Request().Context(), &codeword_rpc.Time{
		To:   int64(req.To),
		From: int64(req.From),
	})

	if err != nil {
		return api.error(ctx, http.StatusInternalServerError, fmt.Sprint("failed getting codeword data", err.Error()))
	}

	userSumMap, er := api.history.GetPayUsersPromoHistory(ctx.Request().Context(), codewordData, int64(req.From), int64(req.To))
	if er != nil {
		return api.error(ctx, http.StatusInternalServerError, fmt.Sprint("failed clculate promo users paid sum", er.Error()))
	}

	resp := make(map[string]struct {
		Regs  int
		Money int64
	})

	for promoID, data := range codewordData {
		fmt.Println("PROTOMOTO", promoID,data)
		for _, value := range data {

			paids, ok := userSumMap[value.UserID]
			if !ok {
				paids = 0
			}
		fmt.Println("PROTOMOTO1", paids, value)

			if value.Time >= int64(req.From) && value.Time <= int64(req.To) {
				if _, ok := resp[promoID]; !ok {
					resp[promoID] = struct {
						Regs  int
						Money int64
					}{Regs: 1, Money: paids}
					continue
				}
				current := resp[promoID]
				current.Regs += 1
				current.Money += paids
				resp[promoID] = current
			}
		}
	}

	return ctx.JSON(http.StatusOK, resp)
}
