package wallet_client

import (
	"context"
	"fmt"
	"gitea.pena/PenaSide/common/log_mw"
	"gitea.pena/PenaSide/customer/internal/errors"
	"gitea.pena/PenaSide/customer/internal/interface/client"
	"gitea.pena/PenaSide/customer/internal/interface/controller/http"
	"gitea.pena/PenaSide/customer/internal/interface/repository"
	"gitea.pena/PenaSide/customer/internal/models"
	"gitea.pena/PenaSide/customer/internal/proto/treasurer"
	"gitea.pena/PenaSide/customer/internal/utils"
	"gitea.pena/PenaSide/customer/pkg/validate"
	"github.com/gofiber/fiber/v2"
	"go.uber.org/zap"
)

type Deps struct {
	MiddleWare     *http.MiddleWare
	AuthClient     *client.AuthClient
	PaymentClient  *client.PaymentClient
	GRPCDomain     string
	AccountRepo    *repository.AccountRepository
	CurrencyClient *client.CurrencyClient
	VerifyClient   *client.VerificationClient
	MailClient     *client.MailClient
	Logger         *zap.Logger
	TelegramClient *client.TelegramClient
}

type WalletController struct {
	middleWare     *http.MiddleWare
	authClient     *client.AuthClient
	paymentClient  *client.PaymentClient
	grpcDomain     string
	accountRepo    *repository.AccountRepository
	currencyClient *client.CurrencyClient
	verifyClient   *client.VerificationClient
	mailClient     *client.MailClient
	logger         *zap.Logger
	telegramClient *client.TelegramClient
}

func NewWalletController(deps Deps) *WalletController {
	return &WalletController{
		middleWare:     deps.MiddleWare,
		authClient:     deps.AuthClient,
		paymentClient:  deps.PaymentClient,
		grpcDomain:     deps.GRPCDomain,
		accountRepo:    deps.AccountRepo,
		currencyClient: deps.CurrencyClient,
		verifyClient:   deps.VerifyClient,
		mailClient:     deps.MailClient,
		logger:         deps.Logger,
		telegramClient: deps.TelegramClient,
	}
}

func (receiver *WalletController) RequestMoney(ctx *fiber.Ctx) error {
	userID, ok := receiver.middleWare.ExtractUserID(ctx)
	if !ok || userID == "" {
		return receiver.middleWare.NoAuth(ctx)
	}
	token, _ := receiver.middleWare.ExtractToken(ctx)

	hlogger := log_mw.ExtractLogger(ctx)

	var request models.GetPaymentLinkBody
	if err := ctx.BodyParser(&request); err != nil {
		return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind payment link")
	}

	if err := utils.ValidateGetPaymentLinkBody(&request); err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	account, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID)
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	link, err := receiver.GetPaymentLink(ctx.Context(), &models.GetPaymentLinkRequest{
		Body:     &request,
		UserID:   userID,
		ClientIP: ctx.IP(),
	}, account, token)
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	hlogger.Emit(models.InfoRequestMoney{
		CtxUserID:      userID,
		CtxAccountID:   account.ID,
		KeyPaymentType: string(request.Type),
		KeyCurrency:    request.Currency,
		CtxPrice:       request.Amount,
		CtxReturnURL:   link,
	})

	return ctx.Status(fiber.StatusOK).JSON(&models.GetPaymentLinkResponse{Link: link})
}

func (receiver *WalletController) GetPaymentLink(ctx context.Context, request *models.GetPaymentLinkRequest, account *models.Account, token string) (string, errors.Error) {
	auth, userErr := receiver.authClient.GetUser(ctx, request.UserID)
	if userErr != nil {
		receiver.logger.Error("failed to get user on <GetPaymentLink> on <PaymentService>",
			zap.Error(userErr),
			zap.String("userID", request.UserID),
		)

		return "", userErr
	}

	if auth.PhoneNumber == "+7" {
		auth.PhoneNumber = ""
	}

	verification, err := receiver.verifyClient.GetVerification(ctx, token, request.UserID)
	if err != nil {
		verification = &models.Verification{
			TaxNumber: "",
		}
	}

	switch request.Body.Type {
	case models.PaymentTypeBankCard:
		return receiver.GetPaymentLinkBankCard(ctx, request, account, auth, verification)
	case models.PaymentTypeYoomoney:
		return receiver.GetPaymentLinkYooMoney(ctx, request, account, auth, verification)
	case models.PaymentTypeSberPay:
		return receiver.GetPaymentLinkSberPay(ctx, request, account, auth, verification)
	case models.PaymentTypeTinkoff:
		return receiver.GetPaymentLinkTinkoff(ctx, request, account, auth, verification)
	case models.PaymentTypeSBP:
		return receiver.GetPaymentLinkSBP(ctx, request, account, auth, verification)
	case models.PaymentTypeSberB2B:
		return receiver.GetPaymentLinkB2B(ctx, request, account, auth, verification)
	}

	return "", errors.NewWithMessage("invalid payment method type", errors.ErrInvalidArgs)
}

func (receiver *WalletController) GetPaymentLinkBankCard(ctx context.Context, request *models.GetPaymentLinkRequest, account *models.Account, auth *models.User, ver *models.Verification) (string, errors.Error) {
	link, err := receiver.paymentClient.GetPaymentLinkBankCard(ctx, &treasurer.GetPaymentLinkRequest{
		MainSettings: &treasurer.MainPaymentSettings{
			Currency:         request.Body.Currency,
			Amount:           request.Body.Amount,
			UserID:           request.UserID,
			ClientIP:         request.ClientIP,
			CallbackHostGRPC: []string{receiver.grpcDomain},
			ReturnURL:        request.Body.ReturnURL,
			Customer: &treasurer.Customer{
				FullName: account.Name.Orgname + ". " + account.Name.FirstName + " " + account.Name.Secondname,
				Email:    auth.Login,
				Phone:    auth.PhoneNumber,
			},
			Items: []*treasurer.Item{&treasurer.Item{
				Description: "Пополнение пользовательского баланса платформы Pena Hub",
				Quantity:    fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
				Currency:    "RUB",
				Money:       fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
			}},
		},
	})
	if err != nil {
		receiver.logger.Error("failed to get bankcard payment link on <GetPaymentLinkBankCard> of <PaymentService>", zap.Error(err))
		return "", err
	}

	return link, nil
}

func (receiver *WalletController) GetPaymentLinkYooMoney(ctx context.Context, request *models.GetPaymentLinkRequest, account *models.Account, auth *models.User, ver *models.Verification) (string, errors.Error) {
	link, err := receiver.paymentClient.GetPaymentLinkYooMoney(ctx, &treasurer.GetPaymentLinkRequest{
		MainSettings: &treasurer.MainPaymentSettings{
			Currency:         request.Body.Currency,
			Amount:           request.Body.Amount,
			UserID:           request.UserID,
			ClientIP:         request.ClientIP,
			CallbackHostGRPC: []string{receiver.grpcDomain},
			ReturnURL:        request.Body.ReturnURL,
			Customer: &treasurer.Customer{
				FullName: account.Name.Orgname + ", " + account.Name.FirstName + " " + account.Name.Secondname,
				Email:    auth.Login,
				Phone:    auth.PhoneNumber,
			},
			Items: []*treasurer.Item{&treasurer.Item{
				Description: "Пополнение пользовательского баланса платформы Pena Hub",
				Quantity:    fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
				Currency:    "RUB",
				Money:       fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
			}},
		},
	})
	if err != nil {
		receiver.logger.Error("failed to get yoomoney payment link on <GetPaymentLinkYooMoney> of <PaymentService>", zap.Error(err))
		return "", err
	}

	return link, nil
}

func (receiver *WalletController) GetPaymentLinkSberPay(ctx context.Context, request *models.GetPaymentLinkRequest, account *models.Account, auth *models.User, ver *models.Verification) (string, errors.Error) {
	link, err := receiver.paymentClient.GetPaymentLinkSberPay(ctx, &treasurer.GetPaymentLinkRequest{
		MainSettings: &treasurer.MainPaymentSettings{
			Currency:         request.Body.Currency,
			Amount:           request.Body.Amount,
			UserID:           request.UserID,
			ClientIP:         request.ClientIP,
			CallbackHostGRPC: []string{receiver.grpcDomain},
			ReturnURL:        request.Body.ReturnURL,
			Customer: &treasurer.Customer{
				FullName: account.Name.Orgname + ", " + account.Name.FirstName + " " + account.Name.Secondname,
				Email:    auth.Login,
				Phone:    auth.PhoneNumber,
			},
			Items: []*treasurer.Item{&treasurer.Item{
				Description: "Пополнение пользовательского баланса платформы Pena Hub",
				Quantity:    fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
				Currency:    "RUB",
				Money:       fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
			}},
		},
	})
	if err != nil {
		receiver.logger.Error("failed to get sberpay payment link on <GetPaymentLinkSberPay> of <PaymentService>", zap.Error(err))
		return "", err
	}

	return link, nil
}

func (receiver *WalletController) GetPaymentLinkTinkoff(ctx context.Context, request *models.GetPaymentLinkRequest, account *models.Account, auth *models.User, ver *models.Verification) (string, errors.Error) {
	link, err := receiver.paymentClient.GetPaymentLinkTinkoff(ctx, &treasurer.GetPaymentLinkRequest{
		MainSettings: &treasurer.MainPaymentSettings{
			Currency:         request.Body.Currency,
			Amount:           request.Body.Amount,
			UserID:           request.UserID,
			ClientIP:         request.ClientIP,
			CallbackHostGRPC: []string{receiver.grpcDomain},
			ReturnURL:        request.Body.ReturnURL,
			Customer: &treasurer.Customer{
				FullName: account.Name.Orgname + ", " + account.Name.FirstName + " " + account.Name.Secondname,
				Email:    auth.Login,
				Phone:    auth.PhoneNumber,
			},
			Items: []*treasurer.Item{&treasurer.Item{
				Description: "Пополнение пользовательского баланса платформы Pena Hub",
				Quantity:    fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
				Currency:    "RUB",
				Money:       fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
			}},
		},
	})
	if err != nil {
		receiver.logger.Error("failed to get tinkoff payment link on <GetPaymentLinkTinkoff> of <PaymentService>", zap.Error(err))
		return "", err
	}

	return link, nil
}

func (receiver *WalletController) GetPaymentLinkSBP(ctx context.Context, request *models.GetPaymentLinkRequest, account *models.Account, auth *models.User, ver *models.Verification) (string, errors.Error) {
	link, err := receiver.paymentClient.GetPaymentLinkSBP(ctx, &treasurer.GetPaymentLinkRequest{
		MainSettings: &treasurer.MainPaymentSettings{
			Currency:         request.Body.Currency,
			Amount:           request.Body.Amount,
			UserID:           request.UserID,
			ClientIP:         request.ClientIP,
			CallbackHostGRPC: []string{receiver.grpcDomain},
			ReturnURL:        request.Body.ReturnURL,
			Customer: &treasurer.Customer{
				FullName: account.Name.Orgname + ", " + account.Name.FirstName + " " + account.Name.Secondname,
				Email:    auth.Login,
				Phone:    auth.PhoneNumber,
			},
			Items: []*treasurer.Item{&treasurer.Item{
				Description: "Пополнение пользовательского баланса платформы Pena Hub",
				Quantity:    fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
				Currency:    "RUB",
				Money:       fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
			}},
		},
	})
	if err != nil {
		receiver.logger.Error("failed to get sbp payment link on <GetPaymentLinkSBP> of <PaymentService>", zap.Error(err))
		return "", err
	}

	return link, nil
}

func (receiver *WalletController) GetPaymentLinkB2B(ctx context.Context, request *models.GetPaymentLinkRequest, account *models.Account, auth *models.User, ver *models.Verification) (string, errors.Error) {
	link, err := receiver.paymentClient.GetPaymentLinkSberbankB2B(ctx, &treasurer.GetPaymentLinkRequest{
		MainSettings: &treasurer.MainPaymentSettings{
			Currency:         request.Body.Currency,
			Amount:           request.Body.Amount,
			UserID:           request.UserID,
			ClientIP:         request.ClientIP,
			CallbackHostGRPC: []string{receiver.grpcDomain},
			ReturnURL:        request.Body.ReturnURL,
			Customer: &treasurer.Customer{
				FullName: account.Name.Orgname + ", " + account.Name.FirstName + " " + account.Name.Secondname,
				Email:    auth.Login,
				Phone:    auth.PhoneNumber,
			},
			Items: []*treasurer.Item{&treasurer.Item{
				Description: "Пополнение пользовательского баланса платформы Pena Hub",
				Quantity:    fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
				Currency:    "RUB",
				Money:       fmt.Sprintf("%.2f", float64(request.Body.Amount)/100),
			}},
		},
	})
	if err != nil {
		receiver.logger.Error("failed to get sberbankb2b payment link on <GetPaymentLinkB2B> of <PaymentService>", zap.Error(err))
		return "", err
	}

	return link, nil
}

func (receiver *WalletController) ChangeCurrency(ctx *fiber.Ctx) error {
	userID, ok := receiver.middleWare.ExtractUserID(ctx)
	if !ok || userID == "" {
		return receiver.middleWare.NoAuth(ctx)
	}

	var request models.ChangeCurrency
	if err := ctx.BodyParser(&request); err != nil {
		return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind currency")
	}

	if validate.IsStringEmpty(request.Currency) {
		return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "empty currency")
	}

	currency := request.Currency
	account, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID)
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	cash, err := receiver.currencyClient.Translate(ctx.Context(), &models.TranslateCurrency{
		Money: account.Wallet.Cash,
		From:  account.Wallet.Currency,
		To:    currency,
	})
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	updatedAccount, err := receiver.accountRepo.ChangeWallet(ctx.Context(), account.UserID, &models.Wallet{
		Cash:     cash,
		Currency: currency,
		Money:    account.Wallet.Money,
	})
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	return ctx.Status(fiber.StatusOK).JSON(updatedAccount)
}

func (receiver *WalletController) PostWalletRspay(ctx *fiber.Ctx) error {
	userID, ok := receiver.middleWare.ExtractUserID(ctx)
	if !ok || userID == "" {
		return receiver.middleWare.NoAuth(ctx)
	}

	hlogger := log_mw.ExtractLogger(ctx)

	var req struct {
		Money *float32 `json:"money,omitempty"`
	}

	if err := ctx.BodyParser(&req); err != nil {
		receiver.logger.Error("failed to bind request", zap.Error(err))
		return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind request")
	}

	user, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID)
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	if user.Status != models.AccountStatusNko && user.Status != models.AccountStatusOrg {
		return receiver.middleWare.Error(ctx, fiber.StatusForbidden, "not allowed for non organizations")
	}
	token := ctx.Get("Authorization")

	verification, err := receiver.verifyClient.GetVerification(ctx.Context(), token, userID)
	if err == errors.ErrNotFound {
		return receiver.middleWare.Error(ctx, fiber.StatusForbidden, "no verification data found")
	}

	authData, err := receiver.authClient.GetUser(ctx.Context(), userID)
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	err = receiver.mailClient.SendMessage(authData.Login, verification, *req.Money)
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	err = receiver.telegramClient.NotifyRsPay(authData.Login, verification, *req.Money)
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	hlogger.Emit(models.InfoRSPay{
		CtxUserID:    userID,
		CtxAccountID: user.ID,
		CtxPrice:     int64(*req.Money),
		CtxLogin:     authData.Login,
	})

	return ctx.SendStatus(fiber.StatusOK)
}
