package client

import (
	"context"
	"fmt"
	"log"

	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/treasurer"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/validate"
)

type PaymentClientDeps struct {
	Logger             *zap.Logger
	PaymentServiceHost string
}

type PaymentClient struct {
	logger             *zap.Logger
	paymentServiceHost string
}

func NewPaymentClient(deps PaymentClientDeps) *PaymentClient {
	if deps.Logger == nil {
		log.Panicln("logger is nil on <NewPaymentClient>")
	}

	if validate.IsStringEmpty(deps.PaymentServiceHost) {
		log.Panicln("payment host is empty on <NewPaymentClient>")
	}

	return &PaymentClient{
		logger:             deps.Logger,
		paymentServiceHost: deps.PaymentServiceHost,
	}
}

func (receiver *PaymentClient) GetPaymentLinkBankCard(ctx context.Context, request *treasurer.GetPaymentLinkRequest) (string, errors.Error) {
	client, closeConnection, connectErr := receiver.connect(ctx)
	if connectErr != nil {
		receiver.logger.Error("failed to connect on <GetPaymentLinkBankCard> of <PaymentClient>", zap.Error(connectErr))
		return "", errors.New(fmt.Errorf("failed connect to payment service: %w", connectErr), errors.ErrInternalError)
	}
	defer closeConnection()

	response, err := client.GetPaymentLinkBankCard(ctx, request)
	if err != nil {
		receiver.logger.Error("failed to get payment link on <GetPaymentLinkBankCard> of <PaymentClient>",
			zap.Error(err),
			zap.String("host", receiver.paymentServiceHost),
		)

		return "", errors.New(fmt.Errorf("failed to get payment link: %w", err), errors.ErrInternalError)
	}

	return response.RedirectURL, nil
}

func (receiver *PaymentClient) GetPaymentLinkYooMoney(ctx context.Context, request *treasurer.GetPaymentLinkRequest) (string, errors.Error) {
	client, closeConnection, connectErr := receiver.connect(ctx)
	if connectErr != nil {
		receiver.logger.Error("failed to connect on <GetPaymentLinkYooMoney> of <PaymentClient>", zap.Error(connectErr))
		return "", errors.New(fmt.Errorf("failed connect to payment service: %w", connectErr), errors.ErrInternalError)
	}
	defer closeConnection()

	response, err := client.GetPaymentLinkYooMoney(ctx, request)
	if err != nil {
		receiver.logger.Error("failed to get payment link on <GetPaymentLinkYooMoney> of <PaymentClient>",
			zap.Error(err),
			zap.String("host", receiver.paymentServiceHost),
		)

		return "", errors.New(fmt.Errorf("failed to get payment link: %w", err), errors.ErrInternalError)
	}

	return response.RedirectURL, nil
}

func (receiver *PaymentClient) GetPaymentLinkSberPay(ctx context.Context, request *treasurer.GetPaymentLinkRequest) (string, errors.Error) {
	client, closeConnection, connectErr := receiver.connect(ctx)
	if connectErr != nil {
		receiver.logger.Error("failed to connect on <GetPaymentLinkSberPay> of <PaymentClient>", zap.Error(connectErr))
		return "", errors.New(fmt.Errorf("failed connect to payment service: %w", connectErr), errors.ErrInternalError)
	}
	defer closeConnection()

	response, err := client.GetPaymentLinkSberPay(ctx, request)
	if err != nil {
		receiver.logger.Error("failed to get payment link on <GetPaymentLinkSberPay> of <PaymentClient>",
			zap.Error(err),
			zap.String("host", receiver.paymentServiceHost),
		)

		return "", errors.New(fmt.Errorf("failed to get payment link: %w", err), errors.ErrInternalError)
	}

	return response.RedirectURL, nil
}

func (receiver *PaymentClient) GetPaymentLinkSBP(ctx context.Context, request *treasurer.GetPaymentLinkRequest) (string, errors.Error) {
	client, closeConnection, connectErr := receiver.connect(ctx)
	if connectErr != nil {
		receiver.logger.Error("failed to connect on <GetPaymentLinkSBP> of <PaymentClient>", zap.Error(connectErr))
		return "", errors.New(fmt.Errorf("failed connect to payment service: %w", connectErr), errors.ErrInternalError)
	}
	defer closeConnection()

	response, err := client.GetPaymentLinkSBP(ctx, request)
	if err != nil {
		receiver.logger.Error("failed to get payment link on <GetPaymentLinkSBP> of <PaymentClient>",
			zap.Error(err),
			zap.String("host", receiver.paymentServiceHost),
		)

		return "", errors.New(fmt.Errorf("failed to get payment link: %w", err), errors.ErrInternalError)
	}

	return response.RedirectURL, nil
}

func (receiver *PaymentClient) GetPaymentLinkTinkoff(ctx context.Context, request *treasurer.GetPaymentLinkRequest) (string, errors.Error) {
	client, closeConnection, connectErr := receiver.connect(ctx)
	if connectErr != nil {
		receiver.logger.Error("failed to connect on <GetPaymentLinkTinkoff> of <PaymentClient>", zap.Error(connectErr))
		return "", errors.New(fmt.Errorf("failed connect to payment service: %w", connectErr), errors.ErrInternalError)
	}
	defer closeConnection()

	response, err := client.GetPaymentLinkTinkoff(ctx, request)
	if err != nil {
		receiver.logger.Error("failed to get payment link on <GetPaymentLinkTinkoff> of <PaymentClient>",
			zap.Error(err),
			zap.String("host", receiver.paymentServiceHost),
		)

		return "", errors.New(fmt.Errorf("failed to get payment link: %w", err), errors.ErrInternalError)
	}

	return response.RedirectURL, nil
}

func (receiver *PaymentClient) GetPaymentLinkSberbankB2B(ctx context.Context, request *treasurer.GetPaymentLinkRequest) (string, errors.Error) {
	client, closeConnection, connectErr := receiver.connect(ctx)
	if connectErr != nil {
		receiver.logger.Error("failed to connect on <GetPaymentLinkSberbankB2B> of <PaymentClient>", zap.Error(connectErr))
		return "", errors.New(fmt.Errorf("failed connect to payment service: %w", connectErr), errors.ErrInternalError)
	}
	defer closeConnection()

	response, err := client.GetPaymentLinkSberbankB2B(ctx, request)
	if err != nil {
		receiver.logger.Error("failed to get payment link on <GetPaymentLinkSberbankB2B> of <PaymentClient>",
			zap.Error(err),
			zap.String("host", receiver.paymentServiceHost),
		)

		return "", errors.New(fmt.Errorf("failed to get payment link: %w", err), errors.ErrInternalError)
	}

	return response.RedirectURL, nil
}

func (receiver *PaymentClient) connect(ctx context.Context) (treasurer.TreasurerServiceClient, func(), errors.Error) {
	connection, err := grpc.DialContext(ctx, receiver.paymentServiceHost, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		receiver.logger.Error("failed connect to payment service no <connect> of <PaymentClient>",
			zap.Error(err),
			zap.String("host", receiver.paymentServiceHost),
		)

		return nil, nil, errors.New(fmt.Errorf("failed connect to payment service: %w", err), errors.ErrInternalError)
	}

	closeConnect := func() {
		if closeErr := connection.Close(); closeErr != nil {
			receiver.logger.Error("failed to close connection on <connect> of <PaymentClient>", zap.Error(closeErr))
		}
	}

	client := treasurer.NewTreasurerServiceClient(connection)

	return client, closeConnect, nil
}
