package payment

import (
	"context"
	"sort"

	"hfb_sys/backend/internal/logging"
	"hfb_sys/backend/internal/model"
	"hfb_sys/backend/internal/modules/order"
	"hfb_sys/backend/internal/modules/paymentconfig"

	"go.uber.org/zap"
	"gorm.io/gorm"
)

type Repository struct {
	db         *gorm.DB
	configRepo *paymentconfig.Repository
	orderRepo  *order.Repository
	logger     *zap.Logger
}

type runtimePaymentConfig struct {
	ID         uint64
	Provider   string
	MerchantID string
	PayWay     string
	JSPayFlag  string
	NotifyURL  string
	JumpURL    string
	Channel    channelClient
}

const (
	channelSourceCreate = "create"
	channelSourceQuery  = "query"
	channelSourceNotify = "notify"
	channelSourceMock   = "mock"
)

var refundBizTypes = []string{
	"cancel_refund",
	"admin_close_refund",
	"admin_refund",
	"checkout_refund",
	"deposit_refund",
	"rent_refund",
	"arbitration_refund",
}

func RefundBizTypes() []string {
	out := make([]string, len(refundBizTypes))
	copy(out, refundBizTypes)
	return out
}

// NewRepository 创建支付仓库，注入支付配置仓库和订单仓库。
func NewRepository(db *gorm.DB, configRepo *paymentconfig.Repository, orderRepo *order.Repository, options ...RepositoryOption) *Repository {
	repo := &Repository{
		db:         db,
		configRepo: configRepo,
		orderRepo:  orderRepo,
		logger:     zap.NewNop(),
	}
	for _, option := range options {
		option(repo)
	}
	if repo.logger == nil {
		repo.logger = zap.NewNop()
	}
	return repo
}

type RepositoryOption func(*Repository)

// WithLogger 为支付仓库注入结构化日志器。
func WithLogger(logger *zap.Logger) RepositoryOption {
	return func(repo *Repository) {
		if logger != nil {
			repo.logger = logger
		}
	}
}

func (r *Repository) log() *zap.Logger {
	if r == nil || r.logger == nil {
		return zap.NewNop()
	}
	return r.logger
}

func paymentLogFields(ctx context.Context, fields ...zap.Field) []zap.Field {
	base := []zap.Field{zap.String("module", "payment")}
	if requestID := logging.RequestIDFromContext(ctx); requestID != "" {
		base = append(base, zap.String("request_id", requestID))
	}
	return append(base, fields...)
}

func runtimeConfigFields(runtimeConfig *runtimePaymentConfig) []zap.Field {
	if runtimeConfig == nil {
		return nil
	}
	return []zap.Field{
		zap.Uint64("payment_config_id", runtimeConfig.ID),
		zap.String("provider", runtimeConfig.Provider),
		zap.String("merchant_id", runtimeConfig.MerchantID),
		zap.String("pay_way", runtimeConfig.PayWay),
	}
}

func paymentOrderFields(payment *model.PaymentOrder) []zap.Field {
	if payment == nil {
		return nil
	}
	return []zap.Field{
		zap.Uint64("payment_id", payment.ID),
		zap.Uint64("order_id", payment.OrderID),
		zap.String("order_no", payment.OrderNo),
		zap.String("third_order_id", payment.ThirdOrderID),
		zap.String("provider_order_id", payment.ProviderOrderID),
		zap.Int64("amount_cent", payment.AmountCent),
		zap.String("biz_type", payment.BizType),
		zap.String("status", payment.Status),
	}
}

func appendFields(groups ...[]zap.Field) []zap.Field {
	var out []zap.Field
	for _, group := range groups {
		out = append(out, group...)
	}
	return out
}

func stringMapKeys(value map[string]string) []string {
	keys := make([]string, 0, len(value))
	for key := range value {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

// isMockMode 判断当前运行时配置是否为模拟支付。
func (c runtimePaymentConfig) isMockMode() bool {
	return c.Provider == "mock"
}

// defaultRuntimeConfig 按支付方式获取默认启用配置，用于新支付单创建。
func (r *Repository) defaultRuntimeConfig(ctx context.Context, payWay string) (*runtimePaymentConfig, error) {
	if r.configRepo == nil {
		return nil, ErrPaymentUnavailable
	}
	dto, err := r.configRepo.FindDefaultByPayWay(ctx, payWay, true)
	if err != nil {
		return nil, err
	}
	return runtimeConfigFromDTO(dto), nil
}

// runtimeConfigForPayment 回溯支付单当时命中的配置，优先使用 payment_config_id。
func (r *Repository) runtimeConfigForPayment(ctx context.Context, payment *model.PaymentOrder) (*runtimePaymentConfig, error) {
	provider := firstNonEmpty(payment.Provider, "mock")
	merchantID := payment.MerchantID
	payWay := normalizePayWay(payment.PayWay)
	if r.configRepo != nil && payment.PaymentConfigID > 0 {
		dto, err := r.configRepo.FindRuntimeByID(ctx, payment.PaymentConfigID, true)
		if err == nil {
			return runtimeConfigFromDTO(dto), nil
		}
		if err != paymentconfig.ErrConfigNotFound {
			return nil, err
		}
	}
	if r.configRepo != nil && merchantID != "" {
		dto, err := r.configRepo.FindByProviderMerchantPayWay(ctx, provider, merchantID, payWay, true)
		if err == nil {
			return runtimeConfigFromDTO(dto), nil
		}
		if err != paymentconfig.ErrConfigNotFound {
			return nil, err
		}
		dto, err = r.configRepo.FindByProviderMerchant(ctx, provider, merchantID, true)
		if err == nil {
			return runtimeConfigFromDTO(dto), nil
		}
		if err != paymentconfig.ErrConfigNotFound {
			return nil, err
		}
	}
	if provider == "mock" {
		return &runtimePaymentConfig{
			Provider:   provider,
			MerchantID: merchantID,
		}, nil
	}
	if provider != "leshua" && provider != "lakala" && provider != "shuncheng" {
		return &runtimePaymentConfig{
			Provider:   provider,
			MerchantID: merchantID,
		}, nil
	}
	if r.configRepo == nil {
		return nil, ErrPaymentUnavailable
	}
	dto, err := r.configRepo.FindDefaultByProviderPayWay(ctx, provider, payWay, true)
	if err != nil {
		return nil, err
	}
	return runtimeConfigFromDTO(dto), nil
}

// runtimeConfigFromDTO 将后台支付配置 DTO 转为支付运行时配置。
func runtimeConfigFromDTO(dto *paymentconfig.ConfigDTO) *runtimePaymentConfig {
	provider := firstNonEmpty(dto.Provider, "mock")
	payWay := normalizePayWay(dto.PayWay)
	jsPayFlag := firstNonEmpty(dto.JSPayFlag, "2")
	client, err := buildChannelClient(dto)
	if err != nil {
		client = nil
	}
	return &runtimePaymentConfig{
		ID:         dto.ID,
		Provider:   provider,
		MerchantID: dto.MerchantID,
		PayWay:     payWay,
		JSPayFlag:  jsPayFlag,
		NotifyURL:  dto.NotifyURL,
		JumpURL:    dto.JumpURL,
		Channel:    client,
	}
}

// recordConfigUsage 记录成功支付使用的配置，失败只写日志不影响主流程。
func (r *Repository) recordConfigUsage(ctx context.Context, runtimeConfig *runtimePaymentConfig, payment *model.PaymentOrder) {
	if r.configRepo == nil || runtimeConfig == nil || payment == nil || runtimeConfig.ID == 0 {
		return
	}
	if payment.BizType != "order_pay" || payment.Status != "paid" {
		return
	}
	if err := r.configRepo.RecordUsage(ctx, runtimeConfig.ID, payment.ID, runtimeConfig.Provider, runtimeConfig.MerchantID, payment.AmountCent, payment.BizType); err != nil {
		r.log().Warn("payment config usage record failed", paymentLogFields(ctx,
			zap.Uint64("payment_config_id", runtimeConfig.ID),
			zap.Uint64("payment_id", payment.ID),
			zap.Error(err),
		)...)
	}
}
