package payment

import (
	"context"
	"time"

	"github.com/sony/gobreaker/v2"
)

type breakerChannel struct {
	next          channelClient
	createPayment *gobreaker.CircuitBreaker[*channelCreatePaymentResponse]
	queryPayment  *gobreaker.CircuitBreaker[*channelQueryPaymentResponse]
	createRefund  *gobreaker.CircuitBreaker[*channelCreateRefundResponse]
	queryRefund   *gobreaker.CircuitBreaker[*channelQueryRefundResponse]
}

func withChannelBreaker(name string, next channelClient) channelClient {
	if next == nil {
		return nil
	}
	return breakerChannel{
		next:          next,
		createPayment: newPaymentBreaker[*channelCreatePaymentResponse](name + ".create_payment"),
		queryPayment:  newPaymentBreaker[*channelQueryPaymentResponse](name + ".query_payment"),
		createRefund:  newPaymentBreaker[*channelCreateRefundResponse](name + ".create_refund"),
		queryRefund:   newPaymentBreaker[*channelQueryRefundResponse](name + ".query_refund"),
	}
}

func newPaymentBreaker[T any](name string) *gobreaker.CircuitBreaker[T] {
	return gobreaker.NewCircuitBreaker[T](gobreaker.Settings{
		Name:        name,
		MaxRequests: 3,
		Timeout:     10 * time.Second,
		ReadyToTrip: func(counts gobreaker.Counts) bool {
			return counts.ConsecutiveFailures >= 5
		},
	})
}

func (c breakerChannel) CreatePayment(ctx context.Context, req channelCreatePaymentRequest) (*channelCreatePaymentResponse, error) {
	return c.createPayment.Execute(func() (*channelCreatePaymentResponse, error) {
		return c.next.CreatePayment(ctx, req)
	})
}

func (c breakerChannel) QueryPayment(ctx context.Context, thirdOrderID string, providerOrderID string) (*channelQueryPaymentResponse, error) {
	return c.queryPayment.Execute(func() (*channelQueryPaymentResponse, error) {
		return c.next.QueryPayment(ctx, thirdOrderID, providerOrderID)
	})
}

func (c breakerChannel) CreateRefund(ctx context.Context, req channelCreateRefundRequest) (*channelCreateRefundResponse, error) {
	return c.createRefund.Execute(func() (*channelCreateRefundResponse, error) {
		return c.next.CreateRefund(ctx, req)
	})
}

func (c breakerChannel) QueryRefund(ctx context.Context, req channelQueryRefundRequest) (*channelQueryRefundResponse, error) {
	return c.queryRefund.Execute(func() (*channelQueryRefundResponse, error) {
		return c.next.QueryRefund(ctx, req)
	})
}

func (c breakerChannel) VerifyNotify(params map[string]string, rawPayload string, contentType string, authorization string) (channelVerifyNotifyResult, error) {
	return c.next.VerifyNotify(params, rawPayload, contentType, authorization)
}
