package payment

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"kra/internal/biz"

	"github.com/go-pay/gopay"
	gopayAlipay "github.com/go-pay/gopay/alipay"
	gopayAlipayV3 "github.com/go-pay/gopay/alipay/v3"
	"github.com/go-pay/gopay/pkg/xhttp"
)

type alipayV3Adapter struct{}

type alipayV3Client struct {
	client  *gopayAlipayV3.ClientV3
	gateway *url.URL
}

func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("支付宝 V3 下单参数为空")
	}
	amount, err := formatDecimalAmount(req.Amount, 100)
	if err != nil {
		return nil, err
	}
	sdk, err := newAlipayV3Client(c)
	if err != nil {
		return nil, err
	}
	method, err := alipayCreateMethod(req.Extra, c)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{
		"out_trade_no": req.TradeNo,
		"total_amount": amount,
		"subject":      req.Subject,
	}
	protected := []string{
		"out_trade_no", "total_amount", "subject", "notify_url", "return_url",
		"method", "pay_method", "trade_type", "channel",
	}
	mergeGoPayConfigExtras(bm, c, "create_extra", protected...)
	mergeMap(bm, req.Extra, protected...)
	if req.NotifyURL != "" {
		bm.Set("notify_url", req.NotifyURL)
	}
	if req.ReturnURL != "" {
		bm.Set("return_url", req.ReturnURL)
	}

	switch method {
	case "alipay.trade.pay":
		if bm.GetString("scene") == "" {
			bm.Set("scene", "bar_code")
		}
		rsp, callErr := sdk.client.TradePay(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if rsp == nil {
			return nil, errors.New("支付宝 V3 付款码支付响应为空")
		}
		if err = alipayV3CheckResponse("付款码支付", rsp.StatusCode, rsp.ErrResponse); err != nil {
			return nil, err
		}
		return alipayV3TradePayResult(req, rsp)
	case "alipay.trade.precreate":
		rsp, callErr := sdk.client.TradePrecreate(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if rsp == nil {
			return nil, errors.New("支付宝 V3 预创建响应为空")
		}
		if err = alipayV3CheckResponse("预创建", rsp.StatusCode, rsp.ErrResponse); err != nil {
			return nil, err
		}
		outTradeNo := strings.TrimSpace(rsp.OutTradeNo)
		if outTradeNo == "" || outTradeNo != req.TradeNo {
			return nil, errors.New("支付宝 V3 预创建响应的 out_trade_no 缺失或不匹配")
		}
		if strings.TrimSpace(rsp.QrCode) == "" {
			return nil, errors.New("支付宝 V3 预创建响应缺少 qr_code")
		}
		return &biz.PaymentResult{
			Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
			QueryID: outTradeNo, Payload: mustJSON(rsp),
		}, nil
	case "alipay.trade.app.pay":
		orderString, callErr := sdk.client.TradeAppPay(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if strings.TrimSpace(orderString) == "" {
			return nil, errors.New("支付宝 V3 APP 支付参数为空")
		}
		return &biz.PaymentResult{
			Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
			Payload: mustJSON(map[string]string{"order_string": orderString}),
		}, nil
	case "alipay.trade.page.pay":
		payURL, callErr := sdk.client.TradePagePay(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		payURL, err = sdk.rewriteGeneratedURL(payURL)
		if err != nil {
			return nil, err
		}
		if strings.TrimSpace(payURL) == "" {
			return nil, errors.New("支付宝 V3 电脑网站支付 URL 为空")
		}
		return &biz.PaymentResult{
			Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
			Payload: mustJSON(map[string]string{"pay_url": payURL}),
		}, nil
	case "alipay.trade.wap.pay":
		payURL, callErr := sdk.client.TradeWapPay(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		payURL, err = sdk.rewriteGeneratedURL(payURL)
		if err != nil {
			return nil, err
		}
		if strings.TrimSpace(payURL) == "" {
			return nil, errors.New("支付宝 V3 手机网站支付 URL 为空")
		}
		return &biz.PaymentResult{
			Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
			Payload: mustJSON(map[string]string{"pay_url": payURL}),
		}, nil
	default:
		if bm.GetString("product_code") == "" {
			bm.Set("product_code", firstAnyOr(c, "JSAPI_PAY", "product_code"))
		}
		if bm.GetString("op_app_id") == "" {
			bm.Set("op_app_id", firstAnyOr(c, text(c, "app_id"), "op_app_id"))
		}
		rsp, callErr := sdk.client.TradeCreate(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if rsp == nil {
			return nil, errors.New("支付宝 V3 创建响应为空")
		}
		if err = alipayV3CheckResponse("创建", rsp.StatusCode, rsp.ErrResponse); err != nil {
			return nil, err
		}
		outTradeNo := strings.TrimSpace(rsp.OutTradeNo)
		if outTradeNo == "" || outTradeNo != req.TradeNo {
			return nil, errors.New("支付宝 V3 创建响应的 out_trade_no 缺失或不匹配")
		}
		providerTradeNo := strings.TrimSpace(rsp.TradeNo)
		if providerTradeNo == "" {
			return nil, errors.New("支付宝 V3 创建响应缺少 trade_no")
		}
		return &biz.PaymentResult{
			Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
			ProviderTradeNo: providerTradeNo, QueryID: outTradeNo, Payload: mustJSON(rsp),
		}, nil
	}
}

func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
	tradeNo = strings.TrimSpace(tradeNo)
	if tradeNo == "" {
		return nil, errors.New("支付宝 V3 查单缺少 out_trade_no")
	}
	sdk, err := newAlipayV3Client(c)
	if err != nil {
		return nil, err
	}
	rsp, callErr := sdk.client.TradeQuery(ctx, gopay.BodyMap{"out_trade_no": tradeNo})
	if callErr != nil {
		return nil, callErr
	}
	if rsp == nil {
		return nil, errors.New("支付宝 V3 查询响应为空")
	}
	if err = alipayV3CheckResponse("查单", rsp.StatusCode, rsp.ErrResponse); err != nil {
		return nil, err
	}
	outTradeNo := strings.TrimSpace(rsp.OutTradeNo)
	if outTradeNo == "" || outTradeNo != tradeNo {
		return nil, errors.New("支付宝 V3 查询响应的 out_trade_no 缺失或不匹配")
	}
	result := &biz.PaymentResult{
		Provider: biz.PaymentAlipayV3, Status: normalizePaymentStatus(rsp.TradeStatus, "pending"),
		TradeNo: outTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.TradeNo),
		Currency: alipayV3Currency(c, ""), Payload: mustJSON(rsp),
	}
	if result.Status != "success" {
		return result, nil
	}
	if result.ProviderTradeNo == "" {
		return nil, errors.New("支付宝 V3 查询响应缺少 trade_no")
	}
	result.Amount, err = parseDecimalAmount(rsp.TotalAmount, 100)
	if err != nil {
		return nil, fmt.Errorf("解析支付宝 V3 订单金额: %w", err)
	}
	values, err := alipayResponseValues(rsp)
	if err != nil {
		return nil, err
	}
	if err = populateAlipayBreakdown(result, values); err != nil {
		return nil, err
	}
	return result, nil
}

func (a *alipayV3Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("支付宝 V3 退款请求为空")
	}
	tradeNo := strings.TrimSpace(req.TradeNo)
	refundNo := strings.TrimSpace(req.RefundNo)
	if tradeNo == "" || refundNo == "" {
		return nil, errors.New("支付宝 V3 退款缺少 out_trade_no 或 out_request_no")
	}
	amount, err := formatDecimalAmount(req.Amount, 100)
	if err != nil {
		return nil, err
	}
	sdk, err := newAlipayV3Client(c)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{
		"out_trade_no":   tradeNo,
		"out_request_no": refundNo,
		"refund_amount":  amount,
	}
	mergeGoPayConfigExtras(bm, c, "refund_extra", "out_trade_no", "out_request_no", "refund_amount")
	rsp, callErr := sdk.client.TradeRefund(ctx, bm)
	if callErr != nil {
		return nil, callErr
	}
	if rsp == nil {
		return nil, errors.New("支付宝 V3 退款响应为空")
	}
	if err = alipayV3CheckResponse("退款", rsp.StatusCode, rsp.ErrResponse); err != nil {
		return nil, err
	}
	outTradeNo := strings.TrimSpace(rsp.OutTradeNo)
	if outTradeNo == "" || outTradeNo != tradeNo {
		return nil, errors.New("支付宝 V3 退款响应的 out_trade_no 缺失或不匹配")
	}
	providerTradeNo := strings.TrimSpace(rsp.TradeNo)
	if providerTradeNo == "" {
		return nil, errors.New("支付宝 V3 退款响应缺少 trade_no")
	}
	if expected := strings.TrimSpace(req.ProviderTradeNo); expected != "" && providerTradeNo != expected {
		return nil, errors.New("支付宝 V3 退款响应的 trade_no 不匹配")
	}
	refundedAmount, err := parseDecimalAmount(rsp.RefundFee, 100)
	if err != nil {
		return nil, fmt.Errorf("解析支付宝 V3 退款金额: %w", err)
	}
	if refundedAmount != req.Amount {
		return nil, errors.New("支付宝 V3 退款响应金额不匹配")
	}
	return &biz.PaymentResult{
		Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: tradeNo,
		ProviderTradeNo: providerTradeNo, Amount: refundedAmount,
		Currency: alipayV3Currency(c, req.Currency), Payload: mustJSON(rsp),
	}, nil
}

func (a *alipayV3Adapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
	if callback == nil || len(callback.Body) == 0 {
		return nil, errors.New("支付宝 V3 回调为空")
	}
	fields := callbackFields(callback)
	if fields["sign"] == "" {
		if values, parseErr := url.ParseQuery(string(callback.Body)); parseErr == nil {
			for key, value := range values {
				if len(value) > 0 {
					fields[key] = value[0]
				}
			}
		}
	}
	if err := verifyAlipayV3Callback(fields, c); err != nil {
		return nil, err
	}
	if fields["app_id"] != text(c, "app_id") {
		return nil, errors.New("支付宝 V3 回调 app_id 不匹配")
	}
	tradeNo := strings.TrimSpace(fields["out_trade_no"])
	providerTradeNo := strings.TrimSpace(fields["trade_no"])
	if tradeNo == "" || providerTradeNo == "" {
		return nil, errors.New("支付宝 V3 回调缺少 out_trade_no 或 trade_no")
	}
	payload, _ := json.Marshal(fields)
	return &biz.PaymentResult{
		Provider: biz.PaymentAlipayV3, Status: normalizePaymentStatus(fields["trade_status"], "pending"),
		TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Payload: payload,
	}, nil
}

func alipayV3TradePayResult(req *biz.PaymentRequest, rsp *gopayAlipayV3.TradePayRsp) (*biz.PaymentResult, error) {
	if req == nil || rsp == nil {
		return nil, errors.New("支付宝 V3 付款码支付响应为空")
	}
	outTradeNo := strings.TrimSpace(rsp.OutTradeNo)
	if outTradeNo == "" || outTradeNo != req.TradeNo {
		return nil, errors.New("支付宝 V3 付款码支付响应的 out_trade_no 缺失或不匹配")
	}
	providerTradeNo := strings.TrimSpace(rsp.TradeNo)
	if providerTradeNo == "" {
		return nil, errors.New("支付宝 V3 付款码支付响应缺少 trade_no")
	}
	amount, err := parseDecimalAmount(rsp.TotalAmount, 100)
	if err != nil {
		return nil, fmt.Errorf("解析支付宝 V3 付款码支付金额: %w", err)
	}
	if amount != req.Amount {
		return nil, errors.New("支付宝 V3 付款码支付响应金额不匹配")
	}
	result := &biz.PaymentResult{
		Provider: biz.PaymentAlipayV3, Status: "success", TradeNo: outTradeNo,
		ProviderTradeNo: providerTradeNo, Amount: amount,
		Currency: strings.ToUpper(strings.TrimSpace(req.Currency)), Payload: mustJSON(rsp),
	}
	if result.Currency == "" {
		result.Currency = "CNY"
	}
	values, err := alipayResponseValues(rsp)
	if err != nil {
		return nil, err
	}
	if err = populateAlipayBreakdown(result, values); err != nil {
		return nil, err
	}
	return result, nil
}

func newAlipayV3Client(c map[string]any) (*alipayV3Client, error) {
	appID := strings.TrimSpace(text(c, "app_id"))
	privateKey, err := alipayV3PrivateKey(c)
	if err != nil {
		return nil, err
	}
	if appID == "" {
		return nil, errors.New("支付宝 V3 缺少 app_id")
	}
	production := !strings.EqualFold(strings.TrimSpace(text(c, "environment")), "sandbox")
	client, err := gopayAlipayV3.NewClientV3(appID, goPayAlipayKey(privateKey, false), production)
	if err != nil {
		return nil, err
	}
	appCert, err := alipayV3AppCertificate(c)
	if err != nil {
		return nil, err
	}
	rootCert, err := alipayV3RootCertificate(c)
	if err != nil {
		return nil, err
	}
	publicCert, err := alipayV3PublicCertificate(c)
	if err != nil {
		return nil, err
	}
	if err = client.SetCert(appCert, rootCert, publicCert); err != nil {
		return nil, fmt.Errorf("配置支付宝 V3 证书: %w", err)
	}
	if token := strings.TrimSpace(text(c, "app_auth_token")); token != "" {
		client.SetAppAuthToken(token)
	}
	client.SetHttpClient(xhttp.NewClient().SetTimeout(20 * time.Second).SetBodySize(8))

	sdk := &alipayV3Client{client: client}
	if endpoint := strings.TrimSpace(text(c, "gateway_url")); endpoint != "" {
		sdk.gateway, err = url.Parse(endpoint)
		if err != nil || sdk.gateway.Scheme == "" || sdk.gateway.Host == "" {
			return nil, fmt.Errorf("支付宝 V3 gateway_url 无效: %s", endpoint)
		}
	}
	apiEndpoint := strings.TrimSpace(firstAny(c, "api_base_url", "proxy_host", "base_url"))
	if apiEndpoint == "" && sdk.gateway != nil && (sdk.gateway.Path == "" || sdk.gateway.Path == "/") {
		apiEndpoint = sdk.gateway.String()
	}
	if apiEndpoint != "" {
		parsed, parseErr := url.Parse(apiEndpoint)
		if parseErr != nil || parsed.Scheme == "" || parsed.Host == "" {
			return nil, fmt.Errorf("支付宝 V3 api_base_url 无效: %s", apiEndpoint)
		}
		client.SetProxyHost(strings.TrimRight(apiEndpoint, "/"))
	}
	return sdk, nil
}

func (c *alipayV3Client) rewriteGeneratedURL(value string) (string, error) {
	if strings.TrimSpace(value) == "" || c.gateway == nil {
		return value, nil
	}
	source, err := url.Parse(value)
	if err != nil {
		return "", fmt.Errorf("解析支付宝 V3 支付 URL: %w", err)
	}
	return alipayTargetURL(source, c.gateway).String(), nil
}

func verifyAlipayV3Callback(fields map[string]string, c map[string]any) error {
	if text(c, "app_id") == "" || fields["sign"] == "" {
		return errors.New("支付宝 V3 回调验签配置或 sign 缺失")
	}
	values := url.Values{}
	for key, value := range fields {
		values.Set(key, value)
	}
	body, err := gopayAlipay.ParseNotifyByURLValues(values)
	if err != nil {
		return err
	}
	publicCert, err := alipayV3PublicCertificate(c)
	if err != nil {
		return err
	}
	ok, err := gopayAlipay.VerifySignWithCert(publicCert, cloneGoPayBodyMap(body))
	if err != nil {
		return err
	}
	if !ok {
		return errors.New("支付宝 V3 回调签名校验失败")
	}
	return nil
}

func alipayV3CheckResponse(operation string, status int, response gopayAlipayV3.ErrResponse) error {
	if status == http.StatusOK {
		return nil
	}
	message := strings.TrimSpace(response.Message)
	if code := strings.TrimSpace(response.Code); code != "" {
		if message == "" {
			message = code
		} else {
			message = code + ": " + message
		}
	}
	if message == "" {
		return fmt.Errorf("支付宝 V3 %s HTTP %d", operation, status)
	}
	return fmt.Errorf("支付宝 V3 %s HTTP %d: %s", operation, status, message)
}

func alipayV3Currency(c map[string]any, fallback string) string {
	currency := strings.ToUpper(strings.TrimSpace(firstAny(c, "currency", "default_currency")))
	if currency == "" {
		currency = strings.ToUpper(strings.TrimSpace(fallback))
	}
	if currency == "" {
		currency = "CNY"
	}
	return currency
}

func alipayV3PrivateKey(c map[string]any) (string, error) {
	if value := strings.TrimSpace(firstAny(c, "private_key_content", "private_key")); value != "" {
		return value, nil
	}
	path := strings.TrimSpace(text(c, "private_key_path"))
	if path == "" {
		return "", errors.New("支付宝 V3 缺少 private_key")
	}
	content, err := os.ReadFile(path)
	if err != nil {
		return "", fmt.Errorf("读取支付宝 V3 private_key: %w", err)
	}
	return string(content), nil
}

func alipayV3AppCertificate(c map[string]any) ([]byte, error) {
	return alipayV3Certificate(c, "app_cert",
		[]string{"app_cert_content", "app_public_cert_content"},
		[]string{"app_cert_path", "app_public_cert_path"},
		[]string{"app_cert", "app_public_cert"},
	)
}

func alipayV3RootCertificate(c map[string]any) ([]byte, error) {
	return alipayV3Certificate(c, "root_cert",
		[]string{"alipay_root_cert_content", "root_cert_content"},
		[]string{"alipay_root_cert_path", "root_cert_path"},
		[]string{"alipay_root_cert", "root_cert"},
	)
}

func alipayV3PublicCertificate(c map[string]any) ([]byte, error) {
	return alipayV3Certificate(c, "public_cert",
		[]string{"alipay_public_cert_content", "public_cert_content"},
		[]string{"alipay_public_cert_path", "public_cert_path"},
		[]string{"alipay_public_cert", "public_cert"},
	)
}

func alipayV3Certificate(c map[string]any, label string, contentKeys, pathKeys, flexibleKeys []string) ([]byte, error) {
	if value := strings.TrimSpace(firstAny(c, contentKeys...)); value != "" {
		return []byte(value), nil
	}
	if path := strings.TrimSpace(firstAny(c, pathKeys...)); path != "" {
		content, err := os.ReadFile(path)
		if err != nil {
			return nil, fmt.Errorf("读取支付宝 V3 %s: %w", label, err)
		}
		return content, nil
	}
	if value := strings.TrimSpace(firstAny(c, flexibleKeys...)); value != "" {
		if strings.Contains(value, "-----BEGIN CERTIFICATE-----") {
			return []byte(value), nil
		}
		content, err := os.ReadFile(value)
		if err != nil {
			return nil, fmt.Errorf("读取支付宝 V3 %s: %w", label, err)
		}
		return content, nil
	}
	return nil, fmt.Errorf("支付宝 V3 缺少 %s", label)
}
