package cart

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

	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/timestamppb"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
	"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/utils"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils/transfer"
)

type accountRepository interface {
	FindByUserID(ctx context.Context, id string) (*models.Account, errors.Error)
	AddItemToCart(ctx context.Context, userID, itemID string) (*models.Account, errors.Error)
	RemoveItemFromCart(ctx context.Context, userID, itemID string) (*models.Account, errors.Error)
	ClearCart(ctx context.Context, userID string) (*models.Account, errors.Error)
}

type hubadminClient interface {
	GetTariff(ctx context.Context, accessToken, tariffID string) (*models.Tariff, errors.Error)
	GetTariffs(ctx context.Context, accessToken string, tarriffIDs []string) ([]models.Tariff, errors.Error)
}

type discountClient interface {
	Apply(context.Context, *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, errors.Error)
}

type walletService interface {
	ReplenishAccountWallet(context.Context, *models.ReplenishAccountWallet) (*models.Account, errors.Error)
	WithdrawAccountWalletMoney(context.Context, *models.WithdrawAccountWallet) (*models.Account, errors.Error)
}

type historyService interface {
	CreateHistory(ctx context.Context, history *models.History) (*models.History, errors.Error)
}

type tariffBrokerService interface {
	SendMany(ctx context.Context, userID string, tariffs []models.Tariff) []errors.Error
}

type Deps struct {
	Logger              *zap.Logger
	Repository          accountRepository
	HubadminClient      hubadminClient
	DiscountClient      discountClient
	WalletService       walletService
	HistoryService      historyService
	TariffBrokerService tariffBrokerService
}

type Service struct {
	logger              *zap.Logger
	repository          accountRepository
	hubadminClient      hubadminClient
	discountClient      discountClient
	walletService       walletService
	historyService      historyService
	tariffBrokerService tariffBrokerService
}

func New(deps Deps) *Service {
	if deps.Logger == nil {
		log.Panicln("logger is nil on <New (cart service)>")
	}

	if deps.Repository == nil {
		log.Panicln("repository is nil on <New (cart service)>")
	}

	if deps.HubadminClient == nil {
		log.Panicln("HubadminClient is nil on <New (cart service)>")
	}

	if deps.DiscountClient == nil {
		log.Panicln("DiscountClient is nil on <New (cart service)>")
	}

	if deps.WalletService == nil {
		log.Panicln("WalletService is nil on <New (cart service)>")
	}

	if deps.HistoryService == nil {
		log.Panicln("HistoryService is nil on <New (cart service)>")
	}

	if deps.TariffBrokerService == nil {
		log.Panicln("TariffBrokerService is nil on <New (cart service)>")
	}

	return &Service{
		logger:              deps.Logger,
		repository:          deps.Repository,
		hubadminClient:      deps.HubadminClient,
		discountClient:      deps.DiscountClient,
		walletService:       deps.WalletService,
		historyService:      deps.HistoryService,
		tariffBrokerService: deps.TariffBrokerService,
	}
}

func (receiver *Service) Remove(ctx context.Context, userID, itemID string) (*models.Account, errors.Error) {
	account, err := receiver.repository.RemoveItemFromCart(ctx, userID, itemID)
	if err != nil {
		receiver.logger.Error("failed to remove item from cart on <Remove> of <CartService>", zap.Error(err))
		return nil, err
	}

	return account, nil
}

func (receiver *Service) Add(ctx context.Context, request *models.AddItemToCart) (*models.Account, errors.Error) {
	tariff, err := receiver.hubadminClient.GetTariff(ctx, request.AccessToken, request.TariffID)
	if err != nil {
		receiver.logger.Error("failed to get tariff on <Add> of <CartService>",
			zap.Error(err),
			zap.String("tariffID", request.TariffID),
			zap.String("accessToken", request.AccessToken),
		)

		return nil, err
	}

	if tariff == nil {
		return nil, errors.New(
			fmt.Errorf("failed to get tariff <%s> on <Add> of <CartService>: tariff not found", request.TariffID),
			errors.ErrNotFound,
		)
	}

	account, err := receiver.repository.AddItemToCart(ctx, request.UserID, request.TariffID)
	if err != nil {
		receiver.logger.Error("failed to add item to cart on <Add> of <CartService>", zap.Error(err))
		return nil, err
	}

	return account, nil
}

func (receiver *Service) Pay(ctx context.Context, accessToken string, userID string) (*models.Account, errors.Error) {
	account, err := receiver.repository.FindByUserID(ctx, userID)
	if err != nil {
		receiver.logger.Error("failed to find account on <Pay> of <CartService>", zap.String("userID", userID), zap.Error(err))
		return nil, err
	}

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

	tariffs, err := receiver.hubadminClient.GetTariffs(ctx, accessToken, account.Cart)
	if err != nil {
		receiver.logger.Error("failed to get tarrifs on <Pay> of <CartService>", zap.Strings("cart", account.Cart), zap.Error(err))
		return nil, err
	}

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

	tariffsAmount := utils.CalculateCartPurchasesAmount(tariffs)

	discountResponse, err := receiver.discountClient.Apply(ctx, &discount.ApplyDiscountRequest{
		UserInformation: &discount.UserInformation{
			ID:                  account.UserID,
			Type:                string(account.Status),
			PurchasesAmount:     uint64(account.Wallet.PurchasesAmount),
			CartPurchasesAmount: tariffsAmount,
		},
		Products: transfer.TariffsToProductInformations(tariffs),
		Date:     timestamppb.New(time.Now()),
	})
	if err != nil {
		receiver.logger.Error("failed to discount on <Pay> of <CartService>", zap.Error(err))
		return nil, err
	}

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

	if account.Wallet.Money < int64(discountResponse.Price) {
		receiver.logger.Error("insufficient funds on <Pay> of <CartService>")
		return nil, errors.New(fmt.Errorf("insufficient funds: %d", int64(discountResponse.Price)-account.Wallet.Money), errors.ErrInsufficientFunds)
	}

	updatedAccount, err := receiver.walletService.WithdrawAccountWalletMoney(ctx, &models.WithdrawAccountWallet{
		Money:   int64(discountResponse.Price),
		Account: account,
	})
	if err != nil {
		receiver.logger.Error("failed to withdraw money on <Pay> of <CartService>", zap.Error(err))
		return nil, err
	}

	go func(tariffs []models.Tariff) {
		if _, historyErr := receiver.historyService.CreateHistory(ctx, &models.History{
			Key:     models.CustomerHistoryKeyPayCart,
			UserID:  account.UserID,
			Comment: "Успешная оплата корзины",
			RawDetails: map[string]any{
				"tariffs": tariffs,
				"price":   discountResponse.Price,
			},
		}); historyErr != nil {
			receiver.logger.Error("failed to insert history on <Pay> of <CartService>", zap.Error(historyErr))
		}
	}(tariffs)

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

	receiver.logger.Error("send to redpanda", zap.String("userID", account.UserID), zap.Any("bought tariffs", tariffs))

	if sendErrors := receiver.tariffBrokerService.SendMany(ctx, account.UserID, tariffs); len(sendErrors) > 0 {
		for _, err := range sendErrors {
			receiver.logger.Error("failed to send tariffs to broker on <Pay> of <CartService>", zap.Error(err))
		}

		return nil, errors.NewWithMessage("failed to send tariffs to broker", errors.ErrInternalError)
	}

	if _, err := receiver.repository.ClearCart(ctx, account.UserID); err != nil {
		receiver.logger.Error("failed to clear cart on <Pay> of <CartService>", zap.Error(err))
		return nil, err
	}

	updatedAccount.Cart = []string{}

	return updatedAccount, nil
}
