package callback

import (
	"context"
	"fmt"
	"gitea.pena/PenaSide/customer/internal/interface/broker/tariff"
	"gitea.pena/PenaSide/customer/internal/interface/client"
	"gitea.pena/PenaSide/customer/internal/interface/repository"
	"gitea.pena/PenaSide/customer/internal/proto/discount"
	"gitea.pena/PenaSide/customer/internal/utils"
	"gitea.pena/PenaSide/customer/internal/utils/transfer"
	"gitea.pena/PenaSide/customer/pkg/validate"
	"gitea.pena/PenaSide/tariffs/pkg/grpc_clients"
	"google.golang.org/protobuf/types/known/timestamppb"
	"log"
	"sync"
	"time"

	"gitea.pena/PenaSide/customer/internal/errors"
	"gitea.pena/PenaSide/customer/internal/models"
	"go.uber.org/zap"
	tb "gopkg.in/tucnak/telebot.v2"
)

type accountRepository interface {
	FindByUserID(context.Context, string) (*models.Account, errors.Error)
}

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

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

type PaymentCallbackServiceDeps struct {
	Logger            *zap.Logger
	AccountRepository *repository.AccountRepository
	WalletService     walletService
	HistoryService    historyService
	Notifier          *tb.Bot
	NotifyChannel     int64
	AdminURL          string
	HistoryRepo       *repository.HistoryRepository
	TariffGrpcClient  *grpc_clients.TariffGRPCClient
	DiscountClient    *client.DiscountClient
	CurrencyClient    *client.CurrencyClient
	Producer          *tariff.Producer
}

type PaymentCallbackService struct {
	logger            *zap.Logger
	accountRepository *repository.AccountRepository
	walletService     walletService
	historyService    historyService
	notifier          *tb.Bot
	notifyChannel     int64
	adminURL          string
	historyRepo       *repository.HistoryRepository
	tariffGrpcClient  *grpc_clients.TariffGRPCClient
	discountClient    *client.DiscountClient
	currencyClient    *client.CurrencyClient
	producer          *tariff.Producer
}

func NewPaymentCallbackService(deps PaymentCallbackServiceDeps) *PaymentCallbackService {
	if deps.Logger == nil {
		log.Panicln("logger is nil on <New (payment callback service)>")
	}

	if deps.AccountRepository == nil {
		log.Panicln("AccountRepository is nil on <New (payment callback service)>")
	}

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

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

	return &PaymentCallbackService{
		logger:            deps.Logger,
		accountRepository: deps.AccountRepository,
		walletService:     deps.WalletService,
		historyService:    deps.HistoryService,
		notifier:          deps.Notifier,
		notifyChannel:     deps.NotifyChannel,
		adminURL:          deps.AdminURL,
	}
}

func (receiver *PaymentCallbackService) SuccessEvent(ctx context.Context, event *models.PaymentEvent) errors.Error {
	account, err := receiver.accountRepository.FindByUserID(ctx, event.UserID)
	if err != nil {
		receiver.logger.Error("failed to get account on <SuccessEvent> of <PaymentCallbackService>", zap.Error(err))
		return err
	}

	if account.Wallet.LastPaymentID == event.PaymentID {
		receiver.logger.Info("payment already was executed on <SuccessEvent> of <PaymentCallbackService>", zap.String("paymentID", event.PaymentID))
		return nil
	}

	if _, err := receiver.walletService.ReplenishAccountWallet(ctx, &models.ReplenishAccountWallet{
		Cash:      event.Amount,
		Currency:  event.Currency,
		PaymentID: event.PaymentID,
		Account:   account,
	}); err != nil {
		receiver.logger.Error("failed to replenish wallet on <SuccessEvent> of <PaymentCallbackService>", zap.Error(err))
		return err
	}

	//go func() {
	if _, err := receiver.historyService.CreateHistory(ctx, &models.History{
		UserID:  account.UserID,
		Comment: event.Message,
		Key:     event.Key,
		RawDetails: models.RawDetails{
			Price:   event.Amount,
			Comment: event.Type + ":" + event.Currency,
		},
	}); err != nil {
		receiver.logger.Error("failed to create history on <SuccessEvent> of <PaymentCallbackService>", zap.Error(err))
		return err
	}
	//}()
	go func() {
		if _, err := receiver.notifier.Send(tb.ChatID(receiver.notifyChannel), fmt.Sprintf(`Внесены деньги %.2f, пользователем %s`, float64(event.Amount/100), receiver.adminURL+"/users/"+account.UserID)); err != nil {
			fmt.Println("TBOER", err)
		}
	}()
	return nil
}

func (receiver *PaymentCallbackService) FailureEvent(ctx context.Context, event *models.PaymentEvent) errors.Error {
	if _, err := receiver.historyService.CreateHistory(ctx, &models.History{
		UserID:  event.UserID,
		Comment: event.Message,
		Key:     event.Key,
		//	RawDetails: fmt.Sprintf("%d%s", event.Amount, event.Currency),
	}); err != nil {
		receiver.logger.Error("failed to create history on <FailureEvent> of <PaymentCallbackService>", zap.Error(err))
		return err
	}

	return nil
}

func (receiver *PaymentCallbackService) CartPurchaseEvent(ctx context.Context, event *models.PaymentEvent) errors.Error {
	account, err := receiver.accountRepository.FindByUserID(ctx, event.UserID)
	if err != nil {
		receiver.logger.Error("failed to get account on <CartPurchaseEvent> of <PaymentCallbackService>", zap.Error(err))
		return err
	}

	if len(account.Cart) == 0 {
		return nil
	}

	tariffs := make([]models.Tariff, len(account.Cart))
	for index, tariffID := range account.Cart {
		resp, err := receiver.tariffGrpcClient.GetTariff(ctx, tariffID)
		if err != nil {
			receiver.logger.Error("failed to get tariff on <GetTariffs> of <PaymentCallbackService>", zap.Error(err), zap.String("tariffID", tariffID))
			return errors.New(
				fmt.Errorf("failed to get tariff on <FindByUserID> of <PaymentCallbackService>: %w", err),
				errors.ErrInternalError,
			)
		}

		pbTariff := resp.Tariff
		privileges := make([]models.Privilege, 0, len(pbTariff.Privileges))
		for _, pbPriv := range pbTariff.Privileges {
			privileges = append(privileges, models.Privilege{
				ID:          pbPriv.Id,
				Name:        pbPriv.Name,
				PrivilegeID: pbPriv.PrivilegeId,
				ServiceKey:  pbPriv.ServiceKey,
				Description: pbPriv.Description,
				Amount:      uint64(pbPriv.Amount),
				Type:        models.PrivilegeType(pbPriv.Type),
				Value:       pbPriv.Value,
				Price:       uint64(pbPriv.Price),
			})
		}

		tariffs[index] = models.Tariff{
			ID:         pbTariff.Id,
			Name:       pbTariff.Name,
			Price:      uint64(pbTariff.Price),
			IsCustom:   pbTariff.IsCustom,
			Privileges: privileges,
			Deleted:    pbTariff.IsDeleted,
		}
	}

	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.Spent),
			CartPurchasesAmount: tariffsAmount,
		},
		Products: transfer.TariffsToProductInformations(tariffs),
		Date:     timestamppb.New(time.Now()),
	})
	if err != nil {
		receiver.logger.Error("failed to apply discount on <CartPurchaseEvent> of <PaymentCallbackService>", zap.Error(err))
		return err
	}

	for _, applied := range discountResponse.AppliedDiscounts {
		if applied.Condition.User != nil && *applied.Condition.User != "" {
			_, err := receiver.discountClient.DeleteDiscount(ctx, &discount.GetDiscountByIDRequest{
				ID: applied.ID,
			})
			if err != nil {
				receiver.logger.Error("failed to delete discount on <CartPurchaseEvent> of <PaymentCallbackService>", zap.Error(err))
				return err
			}
		}
	}

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

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

	if request.Account.Wallet.Currency == models.InternalCurrencyKey {
		_, err = receiver.accountRepository.ChangeWallet(ctx, 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 {
			receiver.logger.Error("failed to change wallet on <CartPurchaseEvent> of <PaymentCallbackService>", zap.Error(err))
			return err
		}
	} else {
		cash, err := receiver.currencyClient.Translate(ctx, &models.TranslateCurrency{
			Money: request.Money,
			From:  models.InternalCurrencyKey,
			To:    request.Account.Wallet.Currency,
		})
		if err != nil {
			receiver.logger.Error("failed to translate currency on <CartPurchaseEvent> of <PaymentCallbackService>", zap.Error(err))
			return err
		}

		_, err = receiver.accountRepository.ChangeWallet(ctx, 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 {
			receiver.logger.Error("failed to change wallet (non-internal currency) on <CartPurchaseEvent> of <PaymentCallbackService>", zap.Error(err))
			return err
		}
	}

	if _, err := receiver.historyRepo.Insert(ctx, &models.History{
		Key:     models.CustomerHistoryKeyPayCart,
		UserID:  account.UserID,
		Comment: "Успешная оплата корзины",
		RawDetails: models.RawDetails{
			Tariffs: tariffs,
			Price:   int64(discountResponse.Price),
		},
	}); err != nil {
		receiver.logger.Error("failed to insert history on <CartPurchaseEvent> of <PaymentCallbackService>", zap.Error(err))
		return err
	}

	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 := receiver.producer.Send(ctx, account.UserID, &currentTariff); err != nil {
				mutex.Lock()
				defer mutex.Unlock()
				sendErrors = append(sendErrors, err)
			}
		}(tariff)
	}

	waitGroup.Wait()

	if len(sendErrors) > 0 {
		for _, err := range sendErrors {
			receiver.logger.Error("failed to send tariffs to broker on <CartPurchaseEvent> of <PaymentCallbackService>", zap.Error(err))
		}
		return errors.New(fmt.Errorf("failed to send tariffs to broker"), errors.ErrInternalError)
	}

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

	return nil
}
