package payment

import (
	"context"
	"gorm.io/gorm"
	"hfb_sys/backend/internal/model"
	"log"
)

func (r *Repository) HandleLeshuaNotify(ctx context.Context, params map[string]string, rawPayload string, contentType string) (*NotifyResult, error) {
	return r.HandleNotify(ctx, "leshua", params, rawPayload, contentType, "")
}
func (r *Repository) HandleNotify(ctx context.Context, provider string, params map[string]string, rawPayload string, contentType string, authorization string) (*NotifyResult, error) {
	// 退款通知会携带 merchant_refund_id 或 leshua_refund_id。
	if params["merchant_refund_id"] != "" || params["leshua_refund_id"] != "" || params["provider_refund_id"] != "" {
		return r.HandleRefundNotify(ctx, provider, params, rawPayload, contentType, authorization)
	}

	payment, err := r.findPaymentForNotify(ctx, params)
	if err != nil {
		return nil, err
	}
	runtimeConfig, err := r.runtimeConfigForPayment(ctx, payment)
	if err != nil {
		return nil, ErrPaymentUnavailable
	}
	verify, err := r.verifyNotify(ctx, payment, runtimeConfig, params, rawPayload, contentType, authorization)
	if err != nil {
		return nil, err
	}

	if amount := parseCent(params["amount"]); amount > 0 && amount != payment.AmountCent {
		if err := r.recordNotifyDiagnostic(ctx, payment.ID, params, rawPayload, contentType, verify, "amount_mismatch"); err != nil {
			log.Printf("[payment] %s notify diagnostic save failed third_order_id=%s err=%v", runtimeConfig.Provider, params["third_order_id"], err)
		}
		return nil, ErrPaymentVerifyFailed
	}
	raw := withNotifyDiagnostic(params, rawPayload, contentType, verify, "verified")
	if err := r.applyChannelStatus(ctx, payment, normalizeNotifyPaymentStatus(runtimeConfig.Provider, params["status"]), params["pay_time"], raw, channelSourceNotify); err != nil {
		return nil, err
	}
	return &NotifyResult{OK: true, Message: "000000"}, nil
}
func (r *Repository) HandleRefundNotify(ctx context.Context, provider string, params map[string]string, rawPayload string, contentType string, authorization string) (*NotifyResult, error) {
	payment, err := r.findRefundPaymentForNotify(ctx, params)
	if err != nil {
		return nil, err
	}
	runtimeConfig, err := r.runtimeConfigForPayment(ctx, payment)
	if err != nil {
		return nil, ErrPaymentUnavailable
	}
	verify, err := r.verifyNotify(ctx, payment, runtimeConfig, params, rawPayload, contentType, authorization)
	if err != nil {
		return nil, err
	}

	raw := withNotifyDiagnostic(params, rawPayload, contentType, verify, "verified")
	status := normalizeNotifyRefundStatus(provider, params["status"])
	if err := r.applyRefundChannelStatus(ctx, payment, refundChannelStatusUpdate{
		Status:           status,
		ProviderRefundID: firstNonEmpty(params["provider_refund_id"], params["leshua_refund_id"]),
		RefundTime:       params["refund_time"],
		Raw:              raw,
		Source:           channelSourceNotify,
	}); err != nil {
		return nil, err
	}
	return &NotifyResult{OK: true, Message: "000000"}, nil
}
func (r *Repository) findPaymentForNotify(ctx context.Context, params map[string]string) (*model.PaymentOrder, error) {
	thirdOrderID := params["third_order_id"]
	if thirdOrderID == "" {
		return nil, ErrPaymentNotFound
	}
	var payment model.PaymentOrder
	if err := r.db.WithContext(ctx).Where("third_order_id = ?", thirdOrderID).First(&payment).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrPaymentNotFound
		}
		return nil, err
	}
	return &payment, nil
}
func (r *Repository) findRefundPaymentForNotify(ctx context.Context, params map[string]string) (*model.PaymentOrder, error) {
	merchantRefundID := params["merchant_refund_id"]
	if merchantRefundID == "" {
		return nil, ErrPaymentNotFound
	}
	var payment model.PaymentOrder
	if err := r.db.WithContext(ctx).Where("third_order_id = ?", merchantRefundID).First(&payment).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrPaymentNotFound
		}
		return nil, err
	}
	return &payment, nil
}
func (r *Repository) verifyNotify(ctx context.Context, payment *model.PaymentOrder, runtimeConfig *runtimePaymentConfig, params map[string]string, rawPayload string, contentType string, authorization string) (channelVerifyNotifyResult, error) {
	var verify channelVerifyNotifyResult
	if runtimeConfig.isMockMode() {
		return verify, nil
	}
	if runtimeConfig.Channel == nil {
		return verify, ErrPaymentUnavailable
	}
	verify, err := runtimeConfig.Channel.VerifyNotify(params, rawPayload, contentType, authorization)
	if err != nil || !verify.OK {
		log.Printf(
			"[payment] %s notify verify failed payment_id=%d third_order_id=%s got=%s expected=%s keys=%v base_string=%s",
			runtimeConfig.Provider,
			payment.ID,
			params["third_order_id"],
			verify.Got,
			firstNonEmpty(verify.Expected["notify_key"], verify.Expected["notify_cert"], verify.Expected["error"]),
			verify.ParamKeys,
			firstNonEmpty(verify.BaseString["notify_key"], verify.BaseString["notify_cert"]),
		)
		if err := r.recordNotifyDiagnostic(ctx, payment.ID, params, rawPayload, contentType, verify, "verify_failed"); err != nil {
			log.Printf("[payment] %s notify diagnostic save failed payment_id=%d err=%v", runtimeConfig.Provider, payment.ID, err)
		}
		return verify, ErrPaymentVerifyFailed
	}
	log.Printf("[payment] %s notify verified payment_id=%d third_order_id=%s matched_key=%s", runtimeConfig.Provider, payment.ID, params["third_order_id"], verify.MatchedKey)
	return verify, nil
}
func (r *Repository) recordNotifyDiagnostic(ctx context.Context, paymentID uint64, params map[string]string, rawPayload string, contentType string, verify channelVerifyNotifyResult, status string) error {
	if paymentID == 0 {
		return nil
	}
	raw := withNotifyDiagnostic(params, rawPayload, contentType, verify, status)
	return r.db.WithContext(ctx).Model(&model.PaymentOrder{}).
		Where("id = ?", paymentID).
		Update("raw_response", jsonMap(raw)).Error
}
func withNotifyDiagnostic(params map[string]string, rawPayload string, contentType string, verify channelVerifyNotifyResult, status string) map[string]string {
	raw := withRawSource(params, channelSourceNotify)
	raw["_notify_diagnostic_status"] = status
	raw["_raw_payload"] = rawPayload
	raw["_raw_content_type"] = contentType
	raw["_sign_got"] = verify.Got
	raw["_sign_matched_key"] = verify.MatchedKey
	raw["_sign_expected"] = jsonString(verify.Expected)
	raw["_sign_base_strings"] = jsonString(verify.BaseString)
	return raw
}
