package v5pay

import (
	"bytes"
	"context"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/config"
	"hyapp/services/wallet-service/internal/domain/ledger"
	walletservice "hyapp/services/wallet-service/internal/service/wallet"
)

type Client struct {
	cfg        config.V5PayConfig
	httpClient *http.Client
}

func New(cfg config.V5PayConfig) *Client {
	if cfg.HTTPTimeout <= 0 {
		cfg.HTTPTimeout = 10 * time.Second
	}
	return &Client{
		cfg:        cfg,
		httpClient: &http.Client{Timeout: cfg.HTTPTimeout},
	}
}

// CreateOrder 调 V5Pay 收银台接口并返回 checkoutUrl；productType 由后台支付方式配置决定。
func (c *Client) CreateOrder(ctx context.Context, req walletservice.V5PayCreateOrderRequest) (walletservice.V5PayCreateOrderResponse, error) {
	if c == nil || !c.ready() {
		return walletservice.V5PayCreateOrderResponse{}, xerr.New(xerr.Unavailable, "v5pay client is not configured")
	}
	body := map[string]any{
		"merchantNo":         c.cfg.MerchantNo,
		"appKey":             c.cfg.AppKey,
		"sysCountryCode":     strings.ToUpper(strings.TrimSpace(req.CountryCode)),
		"currency":           strings.ToUpper(strings.TrimSpace(req.CurrencyCode)),
		"orderNo":            req.OrderID,
		"amount":             formatV5PayAmount(req.ProviderAmountMinor),
		"productType":        strings.TrimSpace(req.ProductType),
		"callbackUrl":        strings.TrimSpace(req.NotifyURL),
		"redirectUrl":        strings.TrimSpace(req.ReturnURL),
		"merchantParam":      req.OrderID,
		"language":           normalizeV5PayLanguage(req.Language),
		"tradeSummary":       productInfo(req),
		"email":              payerEmail(req),
		"merchantCustomerId": strconv.FormatInt(req.TargetUserID, 10),
	}
	body["sign"] = signFields(body, c.cfg.SecretKey)
	raw, err := json.Marshal(body)
	if err != nil {
		return walletservice.V5PayCreateOrderResponse{}, err
	}
	httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.cfg.APIBaseURL+"/cgi/cashier/v2/payin", bytes.NewReader(raw))
	if err != nil {
		return walletservice.V5PayCreateOrderResponse{}, err
	}
	httpReq.Header.Set("Content-Type", "application/json")
	resp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return walletservice.V5PayCreateOrderResponse{}, err
	}
	defer resp.Body.Close()
	rawResp, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20))
	if err != nil {
		return walletservice.V5PayCreateOrderResponse{}, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return walletservice.V5PayCreateOrderResponse{}, xerr.New(xerr.Unavailable, "v5pay order request failed")
	}
	fields, err := decodeObject(rawResp)
	if err != nil {
		return walletservice.V5PayCreateOrderResponse{}, err
	}
	if code := fields["code"]; code != "" && code != "1000" {
		return walletservice.V5PayCreateOrderResponse{}, v5payGatewayRejectionError{
			cause:   xerr.New(xerr.Unavailable, "v5pay order rejected: "+code+": "+fields["message"]),
			rawJSON: string(rawResp),
		}
	}
	if !verifyFields(fields, c.cfg.SecretKey) {
		return walletservice.V5PayCreateOrderResponse{}, xerr.New(xerr.PermissionDenied, "v5pay signature is invalid")
	}
	if strings.TrimSpace(fields["checkoutUrl"]) == "" {
		return walletservice.V5PayCreateOrderResponse{}, xerr.New(xerr.Unavailable, "v5pay order response is incomplete")
	}
	return walletservice.V5PayCreateOrderResponse{
		OrderID:         req.OrderID,
		ProviderOrderID: req.OrderID,
		PayURL:          fields["checkoutUrl"],
		RawJSON:         string(rawResp),
	}, nil
}

// QueryOrder 主动查询 V5Pay 订单状态；H5 返回页依赖它补偿异步回调延迟或回调丢失的窗口期。
func (c *Client) QueryOrder(ctx context.Context, req walletservice.V5PayQueryOrderRequest) (walletservice.V5PayQueryOrderResponse, error) {
	if c == nil || !c.ready() {
		return walletservice.V5PayQueryOrderResponse{}, xerr.New(xerr.Unavailable, "v5pay client is not configured")
	}
	body := map[string]any{
		"merchantNo":     c.cfg.MerchantNo,
		"appKey":         c.cfg.AppKey,
		"sysCountryCode": strings.ToUpper(strings.TrimSpace(req.CountryCode)),
		"currency":       strings.ToUpper(strings.TrimSpace(req.CurrencyCode)),
		"orderNo":        req.OrderID,
	}
	body["sign"] = signFields(body, c.cfg.SecretKey)
	raw, err := json.Marshal(body)
	if err != nil {
		return walletservice.V5PayQueryOrderResponse{}, err
	}
	httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.cfg.APIBaseURL+"/cgi/payment/v1/payin/query", bytes.NewReader(raw))
	if err != nil {
		return walletservice.V5PayQueryOrderResponse{}, err
	}
	httpReq.Header.Set("Content-Type", "application/json")
	resp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return walletservice.V5PayQueryOrderResponse{}, err
	}
	defer resp.Body.Close()
	rawResp, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20))
	if err != nil {
		return walletservice.V5PayQueryOrderResponse{}, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return walletservice.V5PayQueryOrderResponse{}, xerr.New(xerr.Unavailable, "v5pay order query request failed")
	}
	fields, err := decodeObject(rawResp)
	if err != nil {
		return walletservice.V5PayQueryOrderResponse{}, err
	}
	if code := fields["code"]; code != "" && code != "1000" {
		return walletservice.V5PayQueryOrderResponse{}, v5payGatewayRejectionError{
			cause:   xerr.New(xerr.Unavailable, "v5pay order query rejected: "+code+": "+fields["message"]),
			rawJSON: string(rawResp),
		}
	}
	if !verifyFields(fields, c.cfg.SecretKey) {
		return walletservice.V5PayQueryOrderResponse{}, xerr.New(xerr.PermissionDenied, "v5pay signature is invalid")
	}
	return walletservice.V5PayQueryOrderResponse{
		OrderID:         fields["orderNo"],
		ProviderOrderID: fields["transactionId"],
		Status:          fields["status"],
		Amount:          fields["amount"],
		Currency:        fields["currency"],
		ProductType:     fields["productType"],
		RawJSON:         string(rawResp),
	}, nil
}

// ListProductTypes 查询当前商户应用在指定国家和币种下实际开通的 V5Pay 产品；H5 展示前用它过滤静态配置。
func (c *Client) ListProductTypes(ctx context.Context, req walletservice.V5PayProductTypesRequest) (walletservice.V5PayProductTypesResponse, error) {
	if c == nil || !c.ready() {
		return walletservice.V5PayProductTypesResponse{}, xerr.New(xerr.Unavailable, "v5pay client is not configured")
	}
	query := map[string]any{
		"merchantNo":     c.cfg.MerchantNo,
		"appKey":         c.cfg.AppKey,
		"sysCountryCode": strings.ToUpper(strings.TrimSpace(req.CountryCode)),
		"currency":       strings.ToUpper(strings.TrimSpace(req.CurrencyCode)),
		"nonce":          time.Now().UnixMilli(),
	}
	query["sign"] = signFields(query, c.cfg.SecretKey)
	httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, c.cfg.APIBaseURL+"/cgi/payment/v1/productTypes", nil)
	if err != nil {
		return walletservice.V5PayProductTypesResponse{}, err
	}
	values := httpReq.URL.Query()
	for key, value := range query {
		values.Set(key, valueToString(value))
	}
	httpReq.URL.RawQuery = values.Encode()
	resp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return walletservice.V5PayProductTypesResponse{}, err
	}
	defer resp.Body.Close()
	rawResp, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20))
	if err != nil {
		return walletservice.V5PayProductTypesResponse{}, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return walletservice.V5PayProductTypesResponse{}, xerr.New(xerr.Unavailable, "v5pay product types request failed")
	}
	var payload struct {
		Code       string `json:"code"`
		Message    string `json:"message"`
		MerchantNo string `json:"merchantNo"`
		AppKey     string `json:"appKey"`
		Nonce      any    `json:"nonce"`
		Sign       string `json:"sign"`
		PayinList  []struct {
			ProductType        string `json:"productType"`
			ProductName        string `json:"productName"`
			Description        string `json:"description"`
			Logo               string `json:"logo"`
			Currency           string `json:"currency"`
			ProductMode        any    `json:"productMode"`
			SupportCashierMode int    `json:"supportCashierMode"`
		} `json:"payinList"`
	}
	if err := json.Unmarshal(rawResp, &payload); err != nil {
		return walletservice.V5PayProductTypesResponse{}, err
	}
	if payload.Code != "" && payload.Code != "1000" {
		return walletservice.V5PayProductTypesResponse{}, v5payGatewayRejectionError{
			cause:   xerr.New(xerr.Unavailable, "v5pay product types rejected: "+payload.Code+": "+payload.Message),
			rawJSON: string(rawResp),
		}
	}
	signFields := map[string]string{
		"code":       payload.Code,
		"message":    payload.Message,
		"merchantNo": payload.MerchantNo,
		"appKey":     payload.AppKey,
		"nonce":      valueToString(payload.Nonce),
		"sign":       payload.Sign,
	}
	if !verifyFields(signFields, c.cfg.SecretKey) {
		return walletservice.V5PayProductTypesResponse{}, xerr.New(xerr.PermissionDenied, "v5pay signature is invalid")
	}
	products := make([]walletservice.V5PayProductType, 0, len(payload.PayinList))
	for _, product := range payload.PayinList {
		products = append(products, walletservice.V5PayProductType{
			ProductType:        product.ProductType,
			ProductName:        product.ProductName,
			Description:        product.Description,
			Logo:               product.Logo,
			Currency:           product.Currency,
			ProductMode:        valueToString(product.ProductMode),
			SupportCashierMode: product.SupportCashierMode,
		})
	}
	return walletservice.V5PayProductTypesResponse{PayinList: products, RawJSON: string(rawResp)}, nil
}

// ParseNotification 验签 V5Pay 回调并返回内层业务字段；验签失败不允许业务层入账。
func (c *Client) ParseNotification(notification ledger.V5PayNotification) (walletservice.V5PayNotifyData, error) {
	if c == nil || !c.ready() {
		return walletservice.V5PayNotifyData{}, xerr.New(xerr.Unavailable, "v5pay client is not configured")
	}
	fields := make(map[string]string, len(notification.Fields))
	for key, value := range notification.Fields {
		fields[key] = value
	}
	if !verifyFields(fields, c.cfg.SecretKey) {
		return walletservice.V5PayNotifyData{}, xerr.New(xerr.PermissionDenied, "v5pay signature is invalid")
	}
	if strings.TrimSpace(fields["orderNo"]) == "" {
		return walletservice.V5PayNotifyData{}, xerr.New(xerr.InvalidArgument, "v5pay callback order_no is required")
	}
	return walletservice.V5PayNotifyData{
		OrderID:         fields["orderNo"],
		ProviderOrderID: fields["transactionId"],
		OrderStatus:     fields["status"],
		Amount:          fields["amount"],
		Currency:        fields["currency"],
		ProductType:     fields["productType"],
		RawJSON:         firstNonEmpty(notification.RawJSON, mustJSON(fields)),
	}, nil
}

func (c *Client) ready() bool {
	return strings.TrimSpace(c.cfg.MerchantNo) != "" &&
		strings.TrimSpace(c.cfg.AppKey) != "" &&
		strings.TrimSpace(c.cfg.SecretKey) != "" &&
		strings.TrimSpace(c.cfg.APIBaseURL) != ""
}

type v5payGatewayRejectionError struct {
	cause   error
	rawJSON string
}

func (e v5payGatewayRejectionError) Error() string {
	return e.cause.Error()
}

func (e v5payGatewayRejectionError) Unwrap() error {
	return e.cause
}

func (e v5payGatewayRejectionError) ProviderPayloadJSON() string {
	return e.rawJSON
}

func signFields(fields map[string]any, secretKey string) string {
	normalized := make(map[string]string, len(fields))
	for key, value := range fields {
		normalized[key] = valueToString(value)
	}
	return signStringFields(normalized, secretKey)
}

func verifyFields(fields map[string]string, secretKey string) bool {
	want := strings.ToLower(strings.TrimSpace(fields["sign"]))
	return want != "" && want == signStringFields(fields, secretKey)
}

func signStringFields(fields map[string]string, secretKey string) string {
	keys := make([]string, 0, len(fields))
	for key, value := range fields {
		if key == "sign" || strings.TrimSpace(value) == "" {
			continue
		}
		keys = append(keys, key)
	}
	sort.Strings(keys)
	parts := make([]string, 0, len(keys))
	for _, key := range keys {
		parts = append(parts, key+"="+fields[key])
	}
	sum := md5.Sum([]byte(strings.Join(parts, "&") + secretKey))
	return hex.EncodeToString(sum[:])
}

func decodeObject(raw []byte) (map[string]string, error) {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	var payload map[string]any
	if err := decoder.Decode(&payload); err != nil {
		return nil, err
	}
	fields := make(map[string]string, len(payload))
	for key, value := range payload {
		fields[key] = valueToString(value)
	}
	return fields, nil
}

func valueToString(value any) string {
	switch typed := value.(type) {
	case nil:
		return ""
	case string:
		return typed
	case json.Number:
		return typed.String()
	case float64:
		return strconv.FormatFloat(typed, 'f', -1, 64)
	case bool:
		if typed {
			return "true"
		}
		return "false"
	default:
		return fmt.Sprint(typed)
	}
}

func formatV5PayAmount(minor int64) string {
	return fmt.Sprintf("%d.%02d", minor/100, minor%100)
}

func productInfo(req walletservice.V5PayCreateOrderRequest) string {
	name := strings.TrimSpace(req.ProductName)
	if name == "" {
		name = "coin package"
	}
	return fmt.Sprintf("%s %d coins", name, req.CoinAmount)
}

func payerEmail(req walletservice.V5PayCreateOrderRequest) string {
	if strings.TrimSpace(req.PayerEmail) != "" {
		return strings.TrimSpace(req.PayerEmail)
	}
	return fmt.Sprintf("v5pay-payer-%d@haiyihy.com", req.TargetUserID)
}

func normalizeV5PayLanguage(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "zh", "zh-cn":
		return "zh-CN"
	case "zh-tw", "zh-hk", "zh-tc":
		return "zh-TC"
	case "es", "es-es":
		return "es-ES"
	case "pt", "pt-pt":
		return "pt-PT"
	case "ru", "ru-ru":
		return "ru-RU"
	case "ja", "jp", "ja-jp", "jp-jp":
		return "jp-JP"
	default:
		return "en-US"
	}
}

func mustJSON(value any) string {
	raw, err := json.Marshal(value)
	if err != nil {
		return ""
	}
	return string(raw)
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return value
		}
	}
	return ""
}
