package payment

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"hfb_sys/backend/internal/integrations/payment/lakala"
	"hfb_sys/backend/internal/integrations/payment/leshua"
	"hfb_sys/backend/internal/modules/paymentconfig"
)

type channelClient interface {
	CreatePayment(ctx context.Context, req channelCreatePaymentRequest) (*channelCreatePaymentResponse, error)
	QueryPayment(ctx context.Context, thirdOrderID string, providerOrderID string) (*channelQueryPaymentResponse, error)
	CreateRefund(ctx context.Context, req channelCreateRefundRequest) (*channelCreateRefundResponse, error)
	QueryRefund(ctx context.Context, req channelQueryRefundRequest) (*channelQueryRefundResponse, error)
	VerifyNotify(params map[string]string, rawPayload string, contentType string, authorization string) (channelVerifyNotifyResult, error)
}

type channelCreatePaymentRequest struct {
	ThirdOrderID string
	AmountCent   int64
	PayWay       string
	JSPayFlag    string
	NotifyURL    string
	JumpURL      string
	ClientIP     string
	Body         string
	Attach       string
}

type channelCreatePaymentResponse struct {
	OK              bool
	ErrorMessage    string
	ProviderOrderID string
	PayWay          string
	Status          string
	PayTime         string
	TDCode          string
	JSPayURL        string
	JSPayInfo       string
	RawRequest      map[string]string
	Raw             map[string]string
}

type channelQueryPaymentResponse struct {
	OK              bool
	ErrorMessage    string
	ProviderOrderID string
	Status          string
	Amount          string
	PayWay          string
	PayTime         string
	Raw             map[string]string
}

type channelCreateRefundRequest struct {
	ThirdOrderID     string
	ProviderOrderID  string
	MerchantRefundID string
	RefundAmountCent int64
	NotifyURL        string
	Attach           string
	Remark           string
	ClientIP         string
}

type channelCreateRefundResponse struct {
	OK               bool
	ErrorMessage     string
	ProviderRefundID string
	Status           string
	RefundAmount     string
	RawRequest       map[string]string
	Raw              map[string]string
}

type channelQueryRefundRequest struct {
	ThirdOrderID     string
	ProviderOrderID  string
	MerchantRefundID string
	ProviderRefundID string
}

type channelQueryRefundResponse struct {
	OK               bool
	ErrorMessage     string
	ProviderRefundID string
	Status           string
	RefundAmount     string
	RefundTime       string
	Raw              map[string]string
}

type channelVerifyNotifyResult struct {
	OK         bool
	MatchedKey string
	Got        string
	Expected   map[string]string
	BaseString map[string]string
	ParamKeys  []string
}

type leshuaChannel struct {
	client *leshua.Client
}

func newLeshuaChannel(cfg leshua.Config) channelClient {
	return leshuaChannel{client: leshua.NewClient(cfg)}
}

func (c leshuaChannel) CreatePayment(ctx context.Context, req channelCreatePaymentRequest) (*channelCreatePaymentResponse, error) {
	resp, rawReq, err := c.client.CreatePayment(ctx, leshua.CreatePaymentRequest{
		ThirdOrderID: req.ThirdOrderID,
		AmountCent:   req.AmountCent,
		PayWay:       req.PayWay,
		JSPayFlag:    req.JSPayFlag,
		NotifyURL:    req.NotifyURL,
		JumpURL:      req.JumpURL,
		ClientIP:     req.ClientIP,
		Body:         req.Body,
		Attach:       req.Attach,
	})
	if err != nil {
		return nil, err
	}
	return &channelCreatePaymentResponse{
		OK:              resp.RespCode == "0" && resp.ResultCode == "0",
		ErrorMessage:    resp.ErrorMessage,
		ProviderOrderID: resp.ProviderOrderID,
		PayWay:          resp.PayWay,
		Status:          normalizeLeshuaPaymentStatus(resp.Raw["status"]),
		PayTime:         resp.Raw["pay_time"],
		TDCode:          resp.TDCode,
		JSPayURL:        resp.JSPayURL,
		JSPayInfo:       resp.JSPayInfo,
		RawRequest:      rawReq,
		Raw:             resp.Raw,
	}, nil
}

func (c leshuaChannel) QueryPayment(ctx context.Context, thirdOrderID string, providerOrderID string) (*channelQueryPaymentResponse, error) {
	resp, err := c.client.QueryPayment(ctx, thirdOrderID, providerOrderID)
	if err != nil {
		return nil, err
	}
	return &channelQueryPaymentResponse{
		OK:              resp.RespCode == "0" && resp.ResultCode == "0",
		ErrorMessage:    resp.ErrorMessage,
		ProviderOrderID: resp.ProviderOrderID,
		Status:          normalizeLeshuaPaymentStatus(resp.Status),
		Amount:          resp.Amount,
		PayWay:          resp.PayWay,
		PayTime:         resp.PayTime,
		Raw:             resp.Raw,
	}, nil
}

func (c leshuaChannel) CreateRefund(ctx context.Context, req channelCreateRefundRequest) (*channelCreateRefundResponse, error) {
	resp, rawReq, err := c.client.CreateRefund(ctx, leshua.CreateRefundRequest{
		ThirdOrderID:     req.ThirdOrderID,
		LeshuaOrderID:    req.ProviderOrderID,
		MerchantRefundID: req.MerchantRefundID,
		RefundAmountCent: req.RefundAmountCent,
		NotifyURL:        req.NotifyURL,
		Attach:           req.Attach,
	})
	if err != nil {
		return nil, err
	}
	return &channelCreateRefundResponse{
		OK:               resp.RespCode == "0" && resp.ResultCode == "0",
		ErrorMessage:     resp.ErrorMessage,
		ProviderRefundID: resp.LeshuaRefundID,
		Status:           normalizeLeshuaRefundStatus(resp.Status),
		RefundAmount:     resp.RefundAmount,
		RawRequest:       rawReq,
		Raw:              resp.Raw,
	}, nil
}

func (c leshuaChannel) QueryRefund(ctx context.Context, req channelQueryRefundRequest) (*channelQueryRefundResponse, error) {
	resp, err := c.client.QueryRefund(ctx, leshua.QueryRefundRequest{
		ThirdOrderID:     req.ThirdOrderID,
		LeshuaOrderID:    req.ProviderOrderID,
		MerchantRefundID: req.MerchantRefundID,
		LeshuaRefundID:   req.ProviderRefundID,
	})
	if err != nil {
		return nil, err
	}
	return &channelQueryRefundResponse{
		OK:               resp.RespCode == "0" && resp.ResultCode == "0",
		ErrorMessage:     resp.ErrorMessage,
		ProviderRefundID: resp.LeshuaRefundID,
		Status:           normalizeLeshuaRefundStatus(resp.Status),
		RefundAmount:     resp.RefundAmount,
		RefundTime:       resp.RefundTime,
		Raw:              resp.Raw,
	}, nil
}

func (c leshuaChannel) VerifyNotify(params map[string]string, rawPayload string, contentType string, authorization string) (channelVerifyNotifyResult, error) {
	verify := c.client.VerifyNotifyDetail(params)
	result := channelVerifyNotifyResult{
		OK:         verify.OK,
		MatchedKey: verify.MatchedKey,
		Got:        verify.Got,
		Expected:   verify.Expected,
		BaseString: verify.BaseString,
		ParamKeys:  verify.ParamKeys,
	}
	if !result.OK {
		return result, ErrPaymentVerifyFailed
	}
	return result, nil
}

type lakalaChannel struct {
	client *lakala.Client
}

func newLakalaChannel(cfg lakala.Config) channelClient {
	return lakalaChannel{client: lakala.NewClient(cfg)}
}

func (c lakalaChannel) CreatePayment(ctx context.Context, req channelCreatePaymentRequest) (*channelCreatePaymentResponse, error) {
	resp, err := c.client.CreatePayment(ctx, lakala.CreatePaymentRequest{
		ThirdOrderID: req.ThirdOrderID,
		AmountCent:   req.AmountCent,
		PayWay:       req.PayWay,
		JSPayFlag:    req.JSPayFlag,
		NotifyURL:    req.NotifyURL,
		JumpURL:      req.JumpURL,
		ClientIP:     req.ClientIP,
		Body:         req.Body,
		Attach:       req.Attach,
	})
	if err != nil {
		return nil, err
	}
	return &channelCreatePaymentResponse{
		OK:              resp.OK,
		ErrorMessage:    resp.ErrorMessage,
		ProviderOrderID: resp.ProviderOrderID,
		PayWay:          resp.PayWay,
		Status:          resp.Status,
		PayTime:         resp.PayTime,
		TDCode:          resp.TDCode,
		JSPayURL:        resp.JSPayURL,
		JSPayInfo:       resp.JSPayInfo,
		RawRequest:      resp.RawRequest,
		Raw:             resp.Raw,
	}, nil
}

func (c lakalaChannel) QueryPayment(ctx context.Context, thirdOrderID string, providerOrderID string) (*channelQueryPaymentResponse, error) {
	resp, err := c.client.QueryPayment(ctx, thirdOrderID, providerOrderID)
	if err != nil {
		return nil, err
	}
	return &channelQueryPaymentResponse{
		OK:              resp.OK,
		ErrorMessage:    resp.ErrorMessage,
		ProviderOrderID: resp.ProviderOrderID,
		Status:          resp.Status,
		Amount:          resp.Amount,
		PayWay:          resp.PayWay,
		PayTime:         resp.PayTime,
		Raw:             resp.Raw,
	}, nil
}

func (c lakalaChannel) CreateRefund(ctx context.Context, req channelCreateRefundRequest) (*channelCreateRefundResponse, error) {
	resp, err := c.client.CreateRefund(ctx, lakala.CreateRefundRequest{
		ThirdOrderID:     req.ThirdOrderID,
		ProviderOrderID:  req.ProviderOrderID,
		MerchantRefundID: req.MerchantRefundID,
		RefundAmountCent: req.RefundAmountCent,
		NotifyURL:        req.NotifyURL,
		Attach:           req.Attach,
		RefundReason:     req.Remark,
		ClientIP:         req.ClientIP,
	})
	if err != nil {
		return nil, err
	}
	return &channelCreateRefundResponse{
		OK:               resp.OK,
		ErrorMessage:     resp.ErrorMessage,
		ProviderRefundID: resp.ProviderRefundID,
		Status:           resp.Status,
		RefundAmount:     resp.RefundAmount,
		RawRequest:       resp.RawRequest,
		Raw:              resp.Raw,
	}, nil
}

func (c lakalaChannel) QueryRefund(ctx context.Context, req channelQueryRefundRequest) (*channelQueryRefundResponse, error) {
	resp, err := c.client.QueryRefund(ctx, lakala.QueryRefundRequest{
		ThirdOrderID:     req.ThirdOrderID,
		ProviderOrderID:  req.ProviderOrderID,
		MerchantRefundID: req.MerchantRefundID,
		ProviderRefundID: req.ProviderRefundID,
	})
	if err != nil {
		return nil, err
	}
	return &channelQueryRefundResponse{
		OK:               resp.OK,
		ErrorMessage:     resp.ErrorMessage,
		ProviderRefundID: resp.ProviderRefundID,
		Status:           resp.Status,
		RefundAmount:     resp.RefundAmount,
		RefundTime:       resp.RefundTime,
		Raw:              resp.Raw,
	}, nil
}

func (c lakalaChannel) VerifyNotify(params map[string]string, rawPayload string, contentType string, authorization string) (channelVerifyNotifyResult, error) {
	verify := c.client.VerifyNotifyDetail(rawPayload, authorization)
	result := channelVerifyNotifyResult{
		OK:         verify.OK,
		MatchedKey: verify.MatchedKey,
		Got:        verify.Got,
		Expected:   verify.Expected,
		BaseString: verify.BaseString,
		ParamKeys:  verify.ParamKeys,
	}
	if !result.OK {
		return result, ErrPaymentVerifyFailed
	}
	return result, nil
}

func buildChannelClient(dto *paymentconfig.ConfigDTO) (channelClient, error) {
	switch strings.ToLower(strings.TrimSpace(dto.Provider)) {
	case "leshua":
		return withChannelBreaker("leshua", newLeshuaChannel(leshua.Config{
			GatewayURL: dto.GatewayURL,
			MerchantID: dto.MerchantID,
			SignKey:    dto.SignKey,
			NotifyKey:  dto.NotifyKey,
			NotifyURL:  dto.NotifyURL,
			JumpURL:    dto.JumpURL,
			PayWay:     firstNonEmpty(dto.PayWay, "ZFBZF"),
			JSPayFlag:  firstNonEmpty(dto.JSPayFlag, "2"),
			SignType:   firstNonEmpty(dto.SignType, "MD5"),
		})), nil
	case "lakala":
		return withChannelBreaker("lakala", newLakalaChannel(lakala.Config{
			GatewayURL:         dto.GatewayURL,
			AppID:              extraString(dto.ExtraConfig, "app_id"),
			SerialNo:           extraString(dto.ExtraConfig, "serial_no"),
			MerchantID:         dto.MerchantID,
			TermNo:             extraString(dto.ExtraConfig, "term_no"),
			PrivateKey:         dto.SignKey,
			LakalaCert:         extraString(dto.ExtraConfig, "lakala_cert"),
			NotifyCert:         dto.NotifyKey,
			NotifyURL:          dto.NotifyURL,
			JumpURL:            dto.JumpURL,
			PayWay:             firstNonEmpty(dto.PayWay, "ZFBZF"),
			JSPayFlag:          firstNonEmpty(dto.JSPayFlag, "2"),
			PayMode:            extraString(dto.ExtraConfig, "pay_mode"),
			OrderExpireMinutes: extraInt(dto.ExtraConfig, "order_expire_minutes"),
		})), nil
	case "mock":
		return nil, nil
	default:
		return nil, fmt.Errorf("unsupported payment provider: %s", dto.Provider)
	}
}

func normalizeLeshuaPaymentStatus(status string) string {
	switch status {
	case "2", "30":
		return "paid"
	case "6":
		return "closed"
	case "8":
		return "failed"
	default:
		return "paying"
	}
}

func normalizeLeshuaRefundStatus(status string) string {
	switch status {
	case "11":
		return "refunded"
	case "12":
		return "failed"
	default:
		return "refunding"
	}
}

func normalizeNotifyPaymentStatus(provider string, status string) string {
	if provider == "leshua" {
		return normalizeLeshuaPaymentStatus(status)
	}
	switch status {
	case "paid", "closed", "failed", "paying":
		return status
	default:
		return "paying"
	}
}

func normalizeNotifyRefundStatus(provider string, status string) string {
	if provider == "leshua" {
		return normalizeLeshuaRefundStatus(status)
	}
	switch status {
	case "refunded", "failed", "refunding":
		return status
	default:
		return "refunding"
	}
}

func extraString(config map[string]any, key string) string {
	if config == nil {
		return ""
	}
	value, ok := config[key]
	if !ok || value == nil {
		return ""
	}
	switch typed := value.(type) {
	case string:
		return strings.TrimSpace(typed)
	default:
		return strings.TrimSpace(fmt.Sprint(typed))
	}
}

func extraInt(config map[string]any, key string) int {
	value := extraString(config, key)
	if value == "" {
		return 0
	}
	n, _ := strconv.Atoi(value)
	return n
}
