package payment

import (
	"bytes"
	"encoding/json"
	"fmt"
	bizpayment "kra/internal/biz/payment"
	"net/http"
	"strings"

	"github.com/go-pay/gopay"
	"github.com/go-pay/gopay/douyin"
)

// The adapters in this package intentionally keep GoPay request/response
// models below the data boundary. The business layer only sees PaymentResult.

func callbackRequest(callback *bizpayment.PaymentCallback) (*http.Request, error) {
	request, err := http.NewRequest(http.MethodPost, "http://payment-callback.local", bytes.NewReader(callback.Body))
	if err != nil {
		return nil, err
	}
	for key, value := range callback.Headers {
		request.Header.Set(key, value)
	}
	return request, nil
}

func mergeGoPayExtras(dst gopay.BodyMap, src map[string]any, protected ...string) {
	protectedKeys := make(map[string]struct{}, len(protected))
	for _, key := range protected {
		protectedKeys[key] = struct{}{}
	}
	for key, value := range src {
		if _, ok := protectedKeys[key]; ok {
			continue
		}
		dst[key] = value
	}
}

func mergeGoPayConfigExtras(dst gopay.BodyMap, config map[string]any, key string, protected ...string) {
	extra, ok := config[key].(map[string]any)
	if !ok || extra == nil {
		return
	}
	mergeGoPayExtras(dst, extra, protected...)
}

func anyConfig(c map[string]any, keys ...string) any {
	for _, key := range keys {
		if value, ok := c[key]; ok && fmt.Sprint(value) != "" {
			return value
		}
	}
	return nil
}

func firstAnyOr(values map[string]any, fallback string, keys ...string) string {
	if value := firstAny(values, keys...); value != "" {
		return value
	}
	return fallback
}

func mustJSON(value any) []byte {
	encoded, _ := json.Marshal(value)
	return encoded
}

func douyinError(rsp any) error {
	return fmt.Errorf("抖音支付请求失败: %v", rsp)
}

func qqOK(returnCode, resultCode string) bool {
	return strings.EqualFold(strings.TrimSpace(returnCode), "SUCCESS") && strings.EqualFold(strings.TrimSpace(resultCode), "SUCCESS")
}

// saobeiOK accepts both the documented numeric success code (01) and the
// textual SUCCESS form used by some gateway test/proxy responses. Saobei's
// communication and business codes are independent, so both must explicitly
// report success.
func saobeiOK(returnCode, resultCode string) bool {
	return saobeiSuccessCode(returnCode) && saobeiSuccessCode(resultCode)
}

func saobeiSuccessCode(code string) bool {
	code = strings.TrimSpace(code)
	return strings.EqualFold(code, "01") || strings.EqualFold(code, "SUCCESS")
}

func saobeiProcessingOK(returnCode, resultCode string) bool {
	return saobeiSuccessCode(returnCode) && strings.TrimSpace(resultCode) == "03"
}

func saobeiResultOK(returnCode, resultCode string) bool {
	return saobeiOK(returnCode, resultCode) || saobeiProcessingOK(returnCode, resultCode)
}

func lakalaResponseOK(returnCode, resultCode string, accepted ...string) bool {
	if !strings.EqualFold(strings.TrimSpace(returnCode), "SUCCESS") {
		return false
	}
	resultCode = strings.ToUpper(strings.TrimSpace(resultCode))
	if resultCode == "" {
		return false
	}
	for _, code := range accepted {
		if resultCode == strings.ToUpper(strings.TrimSpace(code)) {
			return true
		}
	}
	return false
}

func lakalaCreateResponseOK(returnCode, resultCode string) bool {
	return lakalaResponseOK(returnCode, resultCode, "SUCCESS", "EXISTS")
}

func lakalaRetailCreateResponseOK(returnCode, resultCode string) bool {
	return lakalaResponseOK(returnCode, resultCode, "PAYING", "PAY_SUCCESS")
}

func lakalaQueryResponseOK(returnCode, resultCode string) bool {
	return lakalaResponseOK(returnCode, resultCode,
		"PAYING", "CREATE_FAIL", "CLOSED", "PAY_FAIL", "PAY_SUCCESS", "PARTIAL_REFUND", "FULL_REFUND")
}

func lakalaRefundResponseOK(returnCode, resultCode string) bool {
	return lakalaResponseOK(returnCode, resultCode,
		"WAITING", "CREATE_FAILED", "SUCCESS", "FAILED", "FINISHED", "CHANGE")
}

func rspMap(value any) map[string]string {
	encoded, _ := json.Marshal(value)
	var object map[string]any
	_ = json.Unmarshal(encoded, &object)
	result := make(map[string]string, len(object))
	for key, val := range object {
		result[key] = fmt.Sprint(val)
	}
	return result
}

func normalizeDouyinState(state string) string {
	switch strings.ToUpper(strings.TrimSpace(state)) {
	case douyin.TradeStateSuccess:
		return "success"
	case douyin.TradeStatePayErr:
		return "failed"
	case douyin.TradeStateClosed:
		return "failed"
	default:
		return "pending"
	}
}

func normalizeRefundState(state string) string {
	switch strings.ToUpper(strings.TrimSpace(state)) {
	case douyin.RefundStatusSuccess, "REFUNDED":
		return "success"
	case douyin.RefundStatusClosed, douyin.RefundStatusAbnormal, "FAILED":
		return "failed"
	default:
		return "pending"
	}
}

func normalizeQQState(state string) string {
	switch strings.ToUpper(strings.TrimSpace(state)) {
	case "SUCCESS":
		return "success"
	case "REFUND", "CLOSED", "PAYERROR", "REVOKED", "NOPAY":
		return "failed"
	default:
		return "pending"
	}
}

func normalizeAllinState(state string) string {
	state = strings.ToUpper(strings.TrimSpace(state))
	switch state {
	case "SUCCESS", "0000":
		return "success"
	case "FAIL", "FAILED":
		return "failed"
	}
	if len(state) == 4 && state >= "3000" && state <= "3999" {
		return "failed"
	}
	return "pending"
}

func normalizeLakalaState(state string) string {
	switch strings.ToUpper(strings.TrimSpace(state)) {
	case "SUCCESS", "PAID", "PAY_SUCCESS", "1":
		return "success"
	case "CLOSED", "FAILED", "FAIL", "CANCELLED", "CREATE_FAIL", "PAY_FAIL", "PARTIAL_REFUND", "FULL_REFUND":
		return "failed"
	default:
		return "pending"
	}
}

func normalizeLakalaRefundState(state string) string {
	switch strings.ToUpper(strings.TrimSpace(state)) {
	case "SUCCESS", "FINISHED":
		return "success"
	case "CREATE_FAILED", "FAILED", "CHANGE":
		return "failed"
	default:
		return "pending"
	}
}

func normalizeSaobeiState(state string) string {
	switch strings.ToUpper(strings.TrimSpace(state)) {
	case "SUCCESS":
		return "success"
	case "CLOSED", "PAYERROR", "REVOKED", "FAILED", "NOPAY":
		return "failed"
	default:
		return "pending"
	}
}
