package payment

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"strconv"
	"strings"
	"time"

	"kra/internal/biz"
	datapayment "kra/internal/integration/payment"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

type paymentRepo struct{ data Provider }

func NewPaymentRepo(data Provider) biz.PaymentRepo { return &paymentRepo{data: data} }

func ensurePaymentIntegrationConfigs(db *gorm.DB) error {
	for _, provider := range biz.SupportedPaymentProviders {
		var row integrationConfigPO
		err := db.Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error
		defaults := biz.DefaultIntegrationConfig(integrationKindPayment, provider)
		if errors.Is(err, gorm.ErrRecordNotFound) {
			encoded, _ := json.Marshal(defaults)
			if err := db.Create(&integrationConfigPO{Kind: integrationKindPayment, Provider: provider, Enabled: false, Config: string(encoded)}).Error; err != nil {
				return err
			}
			continue
		}
		if err != nil {
			return err
		}
		values := map[string]any{}
		_ = json.Unmarshal([]byte(row.Config), &values)
		changed := false
		for key, value := range defaults {
			if _, exists := values[key]; !exists {
				values[key] = value
				changed = true
			}
		}
		if changed {
			encoded, _ := json.Marshal(values)
			if err := db.Model(&row).Update("config", string(encoded)).Error; err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *paymentRepo) row(ctx context.Context, provider string) (*integrationConfigPO, map[string]any, error) {
	var row integrationConfigPO
	if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil, biz.ErrPaymentProviderNotFound
		}
		return nil, nil, err
	}
	if !row.Enabled {
		return nil, nil, fmt.Errorf("支付渠道 %s 未启用", provider)
	}
	values := map[string]any{}
	if err := json.Unmarshal([]byte(row.Config), &values); err != nil {
		return nil, nil, fmt.Errorf("支付配置格式错误: %w", err)
	}
	return &row, values, nil
}

func (r *paymentRepo) adapter(ctx context.Context, provider string) (datapayment.Adapter, map[string]any, error) {
	_, values, err := r.row(ctx, provider)
	if err != nil {
		return nil, nil, err
	}
	adapter, err := datapayment.New(provider)
	return adapter, values, err
}

func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*biz.PaymentTestResult, error) {
	started := time.Now()
	test := &biz.PaymentTestResult{Provider: provider, TradeNo: "", Passed: false, Stages: []biz.PaymentTestStage{}}
	add := func(name, status, message, tradeNo string, since time.Time) {
		test.Stages = append(test.Stages, biz.PaymentTestStage{Name: name, Status: status, Message: message, TradeNo: tradeNo, Duration: time.Since(since).Milliseconds()})
	}
	values, err := r.testRow(ctx, provider)
	if err != nil {
		add("config", "failed", err.Error(), "", started)
		return test, err
	}
	test.Mode = strings.ToLower(strings.TrimSpace(text(values, "environment")))
	configStart := time.Now()
	if err = biz.ValidateIntegrationConfig(biz.IntegrationKindPayment, provider, values); err != nil {
		add("config", "failed", err.Error(), "", configStart)
		return test, err
	}
	add("config", "passed", "支付配置校验通过", "", configStart)
	if err = validatePaymentTestSettings(provider, values); err != nil {
		add("test_settings", "failed", err.Error(), "", time.Now())
		return test, err
	}

	adapter, err := datapayment.New(provider)
	if err != nil {
		add("adapter", "failed", err.Error(), "", time.Now())
		return test, err
	}
	req := paymentTestRequest(provider, values)
	test.TradeNo = req.TradeNo
	orders := &paymentOrderRepo{data: r.data}
	extra, _ := json.Marshal(req.Extra)
	localStart := time.Now()
	order, _, err := orders.CreatePaymentOrder(ctx, &biz.PaymentOrder{
		TradeNo: req.TradeNo, Provider: provider, BusinessType: req.BusinessType, BusinessID: req.BusinessID,
		Subject: req.Subject, PaymentMode: biz.PaymentModeExternal, OriginalAmount: req.Amount, Amount: req.Amount,
		Currency: req.Currency, PaymentStatus: biz.PaymentStatusInitialized, FulfillmentStatus: biz.FulfillmentStatusPending,
		RefundStatus: biz.RefundStatusNone, ConfirmationID: uuid.NewString(), RequestFingerprint: paymentTestFingerprint(req), Extra: extra,
	})
	if err != nil {
		add("local_order", "failed", err.Error(), req.TradeNo, localStart)
		return test, err
	}
	add("local_order", "passed", "本地测试订单已创建", req.TradeNo, localStart)
	createStart := time.Now()
	created, err := adapter.Create(ctx, req, values)
	if err != nil {
		recordPaymentTestError(ctx, r.data, provider, req.TradeNo, err)
		add("create", "failed", err.Error(), req.TradeNo, createStart)
		return test, err
	}
	if created == nil {
		err = errors.New("测试下单响应为空")
		add("create", "failed", err.Error(), req.TradeNo, createStart)
		return test, err
	}
	if err = validatePaymentTestResult(provider, req.TradeNo, created); err != nil {
		recordPaymentTestError(ctx, r.data, provider, req.TradeNo, err)
		add("create", "failed", err.Error(), req.TradeNo, createStart)
		return test, err
	}
	if order, err = orders.RecordPaymentCreate(ctx, provider, req.TradeNo, paymentTestProviderUpdate(created)); err != nil {
		add("local_order", "failed", "记录第三方下单结果失败: "+err.Error(), req.TradeNo, createStart)
		return test, err
	}
	add("create", "passed", "测试订单已提交", req.TradeNo, createStart)
	test.Result = created

	queryID := strings.TrimSpace(created.QueryID)
	if queryID == "" {
		queryID = strings.TrimSpace(created.ProviderTradeNo)
	}
	if queryID == "" {
		queryID = req.TradeNo
	}
	if provider == biz.PaymentApple {
		queryID = strings.TrimSpace(text(values, "test_transaction_id"))
		if queryID == "" {
			err = errors.New("Apple 连通性测试需要配置 test_transaction_id（沙箱交易 ID）")
			add("query", "failed", err.Error(), req.TradeNo, time.Now())
			return test, err
		}
	}
	queryStart := time.Now()
	queried, queryErr := queryPaymentTest(ctx, adapter, queryID, values)
	if queryErr != nil {
		recordPaymentTestError(ctx, r.data, provider, req.TradeNo, queryErr)
		add("query", "failed", queryErr.Error(), req.TradeNo, queryStart)
		return test, queryErr
	}
	if err = validatePaymentTestResult(provider, req.TradeNo, queried); err != nil {
		recordPaymentTestError(ctx, r.data, provider, req.TradeNo, err)
		add("query", "failed", err.Error(), req.TradeNo, queryStart)
		return test, err
	}
	if provider != biz.PaymentApple {
		if order, err = orders.ApplyPaymentResult(ctx, provider, req.TradeNo, paymentTestProviderUpdate(queried)); err != nil {
			add("local_order", "failed", "回写测试查单结果失败: "+err.Error(), req.TradeNo, queryStart)
			return test, err
		}
	}
	if queried == nil {
		err = errors.New("测试查单响应为空")
		add("query", "failed", err.Error(), req.TradeNo, queryStart)
		return test, err
	}
	test.Result = queried
	add("query", "passed", "测试订单查询成功，状态: "+queried.Status, req.TradeNo, queryStart)

	if queried.Status != "success" || provider == biz.PaymentApple {
		message := "订单尚未支付成功，已完成配置、下单和查单连通性测试；请在沙箱完成付款后重试"
		if provider == biz.PaymentApple {
			message = "Apple 退款由 App Store 管理，已完成配置、下单和交易查询测试"
		}
		add("refund", "skipped", message, req.TradeNo, time.Now())
		test.Passed = true
		return test, nil
	}
	refundStart := time.Now()
	order, refundToken, beginErr := orders.BeginPaymentRefund(ctx, provider, req.TradeNo, req.Amount, time.Minute)
	if beginErr != nil {
		add("refund", "failed", beginErr.Error(), req.TradeNo, refundStart)
		return test, beginErr
	}
	refund, refundErr := adapter.Refund(ctx, &biz.PaymentRefundRequest{Provider: provider, TradeNo: req.TradeNo, ProviderTradeNo: order.ProviderTradeNo, QueryID: order.QueryID, RefundNo: order.RefundNo, Amount: req.Amount, TotalAmount: req.Amount, Currency: req.Currency}, values)
	if refundErr != nil {
		recordPaymentTestError(ctx, r.data, provider, req.TradeNo, refundErr)
		add("refund", "failed", refundErr.Error(), req.TradeNo, refundStart)
		return test, refundErr
	}
	if refund == nil {
		err = errors.New("测试退款响应为空")
		add("refund", "failed", err.Error(), req.TradeNo, refundStart)
		return test, err
	}
	if _, err = orders.CompletePaymentRefundRequest(ctx, provider, req.TradeNo, refundToken, true, ""); err != nil {
		add("local_order", "failed", "回写测试退款结果失败: "+err.Error(), req.TradeNo, refundStart)
		return test, err
	}
	test.Result = refund
	add("refund", "passed", "测试退款申请已被渠道接受", req.TradeNo, refundStart)
	test.Passed = true
	test.FullFlow = true
	return test, nil
}

func paymentTestFingerprint(req *biz.PaymentRequest) string {
	raw, _ := json.Marshal(req)
	hash := sha256.Sum256(raw)
	return hex.EncodeToString(hash[:])
}

func paymentTestProviderUpdate(result *biz.PaymentResult) *biz.PaymentProviderUpdate {
	if result == nil {
		return nil
	}
	return &biz.PaymentProviderUpdate{
		Status: result.Status, ProviderStatus: result.Status, ProviderTradeNo: result.ProviderTradeNo, QueryID: result.QueryID,
		Amount: result.Amount, PayerPaidAmount: result.PayerPaidAmount, CashPaidAmount: result.CashPaidAmount,
		PointPaidAmount: result.PointPaidAmount, DiscountAmount: result.DiscountAmount,
		ProviderDiscountAmount: result.ProviderDiscountAmount, MerchantDiscountAmount: result.MerchantDiscountAmount,
		SettlementAmount: result.SettlementAmount, Currency: result.Currency, PayerCurrency: result.PayerCurrency,
		AmountBreakdownKnown: result.AmountBreakdownKnown, CreatePayload: result.Payload,
	}
}

func validatePaymentTestResult(provider, tradeNo string, result *biz.PaymentResult) error {
	if result == nil {
		return errors.New("支付渠道响应为空")
	}
	if strings.TrimSpace(result.Provider) != provider {
		return errors.New("支付渠道响应的 provider 不匹配")
	}
	if value := strings.TrimSpace(result.TradeNo); provider != biz.PaymentApple && value != "" && value != tradeNo {
		return errors.New("支付渠道响应的商户订单号不匹配")
	}
	return nil
}

func queryPaymentTest(ctx context.Context, adapter datapayment.Adapter, queryID string, values map[string]any) (*biz.PaymentResult, error) {
	var result *biz.PaymentResult
	var err error
	for attempt := 0; attempt < 3; attempt++ {
		result, err = adapter.Query(ctx, queryID, values)
		if err == nil {
			return result, nil
		}
		if attempt == 2 {
			break
		}
		timer := time.NewTimer(500 * time.Millisecond)
		select {
		case <-ctx.Done():
			timer.Stop()
			return nil, ctx.Err()
		case <-timer.C:
		}
	}
	return nil, err
}

func recordPaymentTestError(ctx context.Context, data Provider, provider, tradeNo string, err error) {
	if data == nil || data.DB() == nil || err == nil {
		return
	}
	message := err.Error()
	if len(message) > 512 {
		message = message[:512]
	}
	_ = data.DB().WithContext(ctx).Model(&paymentOrderPO{}).Where("provider = ? AND trade_no = ?", provider, tradeNo).Update("last_error", message).Error
}

func (r *paymentRepo) testRow(ctx context.Context, provider string) (map[string]any, error) {
	var row integrationConfigPO
	if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, biz.ErrPaymentProviderNotFound
		}
		return nil, err
	}
	values := map[string]any{}
	if err := json.Unmarshal([]byte(row.Config), &values); err != nil {
		return nil, fmt.Errorf("支付配置格式错误: %w", err)
	}
	return values, nil
}

func paymentTestRequest(provider string, values map[string]any) *biz.PaymentRequest {
	tradeNo := "kra-test-" + time.Now().UTC().Format("20060102150405.000000000")
	amount := configuredInt64(values, "test_amount", 1)
	if amount <= 0 {
		amount = 1
	}
	req := &biz.PaymentRequest{Provider: provider, TradeNo: strings.ReplaceAll(tradeNo, ".", ""), Subject: "Kra 支付渠道连通性测试", Amount: amount, Currency: strings.ToUpper(firstAny(values, "test_currency", "currency", "fee_type")), NotifyURL: text(values, "notify_url"), ReturnURL: text(values, "return_url"), BusinessType: "system_payment_test", BusinessID: uuid.NewString(), Extra: map[string]any{}}
	if req.Currency == "" {
		req.Currency = "CNY"
	}
	if provider == biz.PaymentApple {
		req.TradeNo = uuid.NewString()
		req.Extra["product_id"] = firstAny(values, "product_id", "test_product_id")
	}
	if raw := strings.TrimSpace(text(values, "test_extra")); raw != "" {
		var extra map[string]any
		if json.Unmarshal([]byte(raw), &extra) == nil {
			for key, value := range extra {
				req.Extra[key] = value
			}
		}
	}
	for _, key := range []string{"trade_type", "method", "pay_type", "channel", "openid", "open_id", "auth_code", "authcode", "barcode"} {
		if value := text(values, key); value != "" {
			req.Extra[key] = value
		}
	}
	return req
}

func validatePaymentTestSettings(provider string, values map[string]any) error {
	if !testModeEnabled(values) {
		return errors.New("请先打开 test_mode（允许执行渠道测试）")
	}
	if raw := strings.TrimSpace(text(values, "test_extra")); raw != "" {
		var extra map[string]any
		if err := json.Unmarshal([]byte(raw), &extra); err != nil {
			return fmt.Errorf("test_extra 必须是 JSON 对象: %w", err)
		}
	}
	if provider == biz.PaymentApple && strings.TrimSpace(text(values, "test_transaction_id")) == "" {
		return errors.New("Apple 测试需要 test_transaction_id（沙箱交易 ID）")
	}
	if provider == biz.PaymentApple && strings.TrimSpace(firstAny(values, "test_product_id", "product_id")) == "" {
		return errors.New("Apple 测试需要 test_product_id（沙箱商品 ID）")
	}
	return nil
}

func testModeEnabled(values map[string]any) bool {
	value, exists := values["test_mode"]
	if !exists {
		return false
	}
	switch typed := value.(type) {
	case bool:
		return typed
	case string:
		return strings.EqualFold(strings.TrimSpace(typed), "true") || typed == "1"
	case float64:
		return typed == 1
	default:
		return false
	}
}

func (r *paymentRepo) Create(ctx context.Context, req *biz.PaymentRequest) (*biz.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("支付下单请求为空")
	}
	a, c, err := r.adapter(ctx, req.Provider)
	if err != nil {
		return nil, err
	}
	effective := *req
	effective.NotifyURL = strings.TrimSpace(text(c, "notify_url"))
	effective.ReturnURL = strings.TrimSpace(text(c, "return_url"))
	if paymentCreateRequiresNotifyURL(req.Provider, req.Extra, c) && effective.NotifyURL == "" {
		return nil, fmt.Errorf("支付渠道 %s 未配置服务端 notify_url", req.Provider)
	}
	return a.Create(ctx, &effective, c)
}

func paymentProviderRequiresNotifyURL(provider string) bool {
	switch provider {
	case biz.PaymentApple, biz.PaymentAllinPay, biz.PaymentSaobei, biz.PaymentPayPal:
		return false
	default:
		return true
	}
}

// paymentCreateRequiresNotifyURL keeps the repository-level URL guard aligned
// with the selected provider operation. Synchronous barcode/retail APIs return
// their execution result directly and do not consume notify_url; redirect and
// client-side prepay APIs still require the configured server callback URL.
func paymentCreateRequiresNotifyURL(provider string, extra, config map[string]any) bool {
	if !paymentProviderRequiresNotifyURL(provider) {
		return false
	}
	keys := []string{"method", "pay_method", "trade_type", "pay_type", "channel"}
	switch provider {
	case biz.PaymentAlipay, biz.PaymentAlipayV3:
		keys = []string{"method", "pay_method", "trade_type", "channel"}
	case biz.PaymentWechatV2:
		keys = []string{"trade_type", "pay_type", "method", "pay_method", "channel"}
	case biz.PaymentWechatV3:
		keys = []string{"trade_type", "pay_type", "method"}
	case biz.PaymentQQ:
		keys = []string{"trade_type", "pay_type", "method", "pay_method"}
	case biz.PaymentLakala:
		keys = []string{"method", "pay_method", "trade_type"}
	}
	value := firstAny(extra, keys...)
	if value == "" {
		value = firstAny(config, keys...)
	}
	normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
	switch provider {
	case biz.PaymentAlipay, biz.PaymentAlipayV3:
		return !contains([]string{"pay", "trade_pay", "alipay_trade_pay", "barcode", "barcode_pay", "micropay", "face_to_face"}, normalized)
	case biz.PaymentWechatV2:
		return !contains([]string{"micropay", "micro_pay", "barcode", "barcode_pay", "pay_code", "payment_code"}, normalized)
	case biz.PaymentWechatV3:
		return !contains([]string{"micropay", "micro_pay", "codepay", "code_pay", "barcode", "barcode_pay", "facepay", "face_pay"}, normalized)
	case biz.PaymentQQ:
		return !contains([]string{"micropay", "micro_pay", "barcode", "barcode_pay"}, normalized)
	case biz.PaymentLakala:
		return !contains([]string{"retail", "retail_pay", "micropay", "barcode"}, normalized)
	default:
		return true
	}
}
func (r *paymentRepo) Query(ctx context.Context, provider, tradeNo string) (*biz.PaymentResult, error) {
	a, c, err := r.adapter(ctx, provider)
	if err != nil {
		return nil, err
	}
	return a.Query(ctx, tradeNo, c)
}
func (r *paymentRepo) Refund(ctx context.Context, req *biz.PaymentRefundRequest) (*biz.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("支付退款请求为空")
	}
	a, c, err := r.adapter(ctx, req.Provider)
	if err != nil {
		return nil, err
	}
	return a.Refund(ctx, req, c)
}
func (r *paymentRepo) HandleCallback(ctx context.Context, callback *biz.PaymentCallback) (*biz.PaymentResult, error) {
	if callback == nil {
		return nil, errors.New("支付回调为空")
	}
	a, c, err := r.adapter(ctx, callback.Provider)
	if err != nil {
		return nil, err
	}
	result, err := a.Callback(ctx, callback, c)
	if err != nil {
		return nil, &biz.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)}
	}
	if result == nil {
		err = errors.New("支付回调解析结果为空")
		return nil, &biz.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)}
	}
	result.SuccessAck = paymentCallbackAck(callback.Provider, c, true)
	result.FailureAck = paymentCallbackAck(callback.Provider, c, false)
	if result.Provider != callback.Provider {
		err = errors.New("支付回调渠道不匹配")
		return nil, &biz.PaymentCallbackError{Cause: err, Ack: result.FailureAck}
	}
	result.EventID = paymentCallbackEventID(callback, result)
	return result, nil
}

func paymentCallbackEventID(callback *biz.PaymentCallback, result *biz.PaymentResult) string {
	if result != nil {
		if eventID := strings.TrimSpace(result.EventID); eventID != "" {
			return eventID
		}
	}
	if callback == nil {
		return ""
	}
	fields := callbackFields(callback)
	if eventID := strings.TrimSpace(first(fields, "event_id", "notify_id", "notificationUUID", "id")); eventID != "" {
		return eventID
	}
	hash := sha256.Sum256(append([]byte(callback.Provider+"\x00"), callback.Body...))
	return hex.EncodeToString(hash[:])
}

func paymentCallbackAck(provider string, values map[string]any, success bool) biz.PaymentCallbackAck {
	ack := biz.DefaultPaymentCallbackAck(provider, success)
	prefix := "callback_success_"
	if !success {
		prefix = "callback_failure_"
	}
	if configured := strings.TrimSpace(text(values, prefix+"status")); configured != "" {
		if status, err := strconv.Atoi(configured); err == nil && status >= 200 && status <= 599 {
			ack.StatusCode = status
		}
	}
	if contentType := strings.TrimSpace(text(values, prefix+"content_type")); contentType != "" {
		ack.ContentType = contentType
	}
	if body, exists := values[prefix+"body"]; exists {
		ack.Body = []byte(fmt.Sprint(body))
	}
	return ack
}

func callbackFields(callback *biz.PaymentCallback) map[string]string {
	fields := map[string]string{}
	for key, value := range callback.Query {
		fields[key] = value
	}
	contentType := strings.ToLower(callback.Headers["Content-Type"])
	if strings.Contains(contentType, "application/x-www-form-urlencoded") {
		if values, err := url.ParseQuery(string(callback.Body)); err == nil {
			for key, value := range values {
				if len(value) > 0 {
					fields[key] = value[0]
				}
			}
		}
	}
	var object map[string]any
	if json.Unmarshal(callback.Body, &object) == nil {
		for key, value := range object {
			if text, ok := value.(string); ok {
				fields[key] = text
			}
		}
	}
	return fields
}

func first(values map[string]string, keys ...string) string {
	for _, key := range keys {
		if values[key] != "" {
			return values[key]
		}
	}
	return ""
}

func contains(values []string, value string) bool {
	for _, item := range values {
		if item == value {
			return true
		}
	}
	return false
}

func validatePaymentConfig(provider string, values map[string]any) error {
	return biz.ValidateIntegrationConfig(biz.IntegrationKindPayment, provider, values)
}
