package fuiou

import (
	"crypto"
	"crypto/md5"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/pem"
	"encoding/xml"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"reflect"
	"sort"
	"strings"
	"sync"

	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/transform"

	"go.uber.org/zap"
)

// Client 富友支付客户端
type Client struct {
	InsCd      string          // 机构号
	MchntCd    string          // 商户号
	TermId     string          // 终端号
	ApiURL     string          // 富友 API 地址
	NotifyURL  string          // 支付回调地址
	privateKey *rsa.PrivateKey // 商户私钥（用于签名）
	publicKey  *rsa.PublicKey  // 富友公钥（用于验签）
	logger     *zap.Logger
}

// NewClient 从配置参数构造富友客户端
// privateKeyBase64: 商户 RSA 私钥 Base64 编码（支持 PEM/DER、PKCS1/PKCS8 格式）
// publicKeyBase64: 富友 RSA 公钥 Base64 编码（支持 PEM/DER 格式）
func NewClient(insCd, mchntCd, termId, apiURL, notifyURL, privateKeyBase64, publicKeyBase64 string, logger *zap.Logger) (*Client, error) {
	privKey, err := parsePrivateKey(privateKeyBase64)
	if err != nil {
		return nil, fmt.Errorf("解析商户私钥失败: %w", err)
	}

	pubKey, err := parsePublicKey(publicKeyBase64)
	if err != nil {
		return nil, fmt.Errorf("解析富友公钥失败: %w", err)
	}
	if rsaPublicKeysEqual(pubKey, &privKey.PublicKey) {
		return nil, fmt.Errorf("富友公钥配置错误：当前 fy_public_key 与商户私钥匹配，请配置富友平台提供的验签公钥")
	}

	return &Client{
		InsCd:      insCd,
		MchntCd:    mchntCd,
		TermId:     termId,
		ApiURL:     apiURL,
		NotifyURL:  notifyURL,
		privateKey: privKey,
		publicKey:  pubKey,
		logger:     logger,
	}, nil
}

// Sign 对请求数据签名
// 算法: 按字典序排列非空字段 → key=value&key=value → GBK 编码 → MD5 → RSA PKCS1v15 签名 → Base64
func (c *Client) Sign(data interface{}) (string, error) {
	signStr := buildSignString(data)

	c.logger.Debug("富友签名原文", zap.String("sign_str", signStr))

	// UTF-8 → GBK
	gbkBytes, err := utf8ToGBK([]byte(signStr))
	if err != nil {
		return "", fmt.Errorf("GBK 编码失败: %w", err)
	}

	// MD5
	hash := md5.Sum(gbkBytes)

	// RSA PKCS1v15 签名
	signature, err := rsa.SignPKCS1v15(rand.Reader, c.privateKey, crypto.MD5, hash[:])
	if err != nil {
		return "", fmt.Errorf("RSA 签名失败: %w", err)
	}

	return base64.StdEncoding.EncodeToString(signature), nil
}

// Verify 验证签名
func (c *Client) Verify(data interface{}, sign string) error {
	signStr := buildSignString(data)
	return c.verifySignString(signStr, sign)
}

// verifySignString 使用指定签名原文验证 RSA 签名。
func (c *Client) verifySignString(signStr, sign string) error {
	c.logger.Debug("富友验签原文", zap.String("sign_str", signStr))
	c.logger.Debug("富友签名", zap.String("sign", sign))

	gbkBytes, err := utf8ToGBK([]byte(signStr))
	if err != nil {
		return fmt.Errorf("GBK 编码失败: %w", err)
	}

	hash := md5.Sum(gbkBytes)

	sigBytes, err := base64.StdEncoding.DecodeString(sign)
	if err != nil {
		return fmt.Errorf("Base64 解码签名失败: %w", err)
	}

	if err := rsa.VerifyPKCS1v15(c.publicKey, crypto.MD5, hash[:], sigBytes); err != nil {
		c.logger.Error("富友验签失败诊断",
			zap.Error(err),
			zap.Int("signature_len", len(sigBytes)),
			zap.Int("public_key_bits", c.publicKey.N.BitLen()),
			zap.String("public_key_sha256", rsaPublicKeyFingerprint(c.publicKey)),
			zap.Bool("public_key_matches_private_key", rsaPublicKeysEqual(c.publicKey, &c.privateKey.PublicKey)),
		)
		return err
	}
	return nil
}

// DoRequest 发送 HTTP 请求到富友
// 处理流程: XML 编码 → GBK 转换 → 双重 URL 编码 → POST → URL 解码 → GBK→UTF-8 → XML 解析
func (c *Client) DoRequest(path string, req interface{}, resp interface{}) error {
	// 1. XML 编码
	xmlBytes, err := xml.Marshal(req)
	if err != nil {
		return fmt.Errorf("XML 编码失败: %w", err)
	}

	// 2. 添加 XML 声明并替换编码声明为 GBK
	xmlStr := `<?xml version="1.0" encoding="GBK"?>` + string(xmlBytes)

	// 3. UTF-8 → GBK
	gbkBytes, err := utf8ToGBK([]byte(xmlStr))
	if err != nil {
		return fmt.Errorf("GBK 编码失败: %w", err)
	}

	// 4. 两次 URL 编码
	encoded := url.QueryEscape(url.QueryEscape(string(gbkBytes)))

	// 5. POST 请求
	reqURL := strings.TrimRight(c.ApiURL, "/") + path
	body := "req=" + encoded

	c.logger.Info("富友请求完整报文",
		zap.String("url", reqURL),
		zap.String("path", path),
		zap.String("request_xml", xmlStr),
		zap.String("request_body", body),
	)

	httpResp, err := http.Post(reqURL, "application/x-www-form-urlencoded", strings.NewReader(body))
	if err != nil {
		return fmt.Errorf("HTTP 请求失败: %w", err)
	}
	defer httpResp.Body.Close()

	// 6. 读取响应
	respBody, err := io.ReadAll(httpResp.Body)
	if err != nil {
		return fmt.Errorf("读取响应失败: %w", err)
	}

	// 7. URL 解码
	decoded, err := url.QueryUnescape(string(respBody))
	if err != nil {
		return fmt.Errorf("URL 解码响应失败: %w", err)
	}

	// 8. GBK → UTF-8
	utf8Bytes, err := GBKToUTF8([]byte(decoded))
	if err != nil {
		return fmt.Errorf("UTF-8 转换失败: %w", err)
	}

	// 9. 替换 XML 声明中的编码为 UTF-8
	utf8Str := strings.Replace(string(utf8Bytes), `encoding="GBK"`, `encoding="UTF-8"`, 1)

	// 10. XML 解析
	if err := xml.Unmarshal([]byte(utf8Str), resp); err != nil {
		return fmt.Errorf("XML 解析响应失败: %w", err)
	}

	return nil
}

// buildSignString 构建签名原文
// 提取字段（排除 sign 和 reserved 开头字段），按字典序拼接为 key=value&key=value。
func buildSignString(data interface{}) string {
	return buildSignStringFromMap(structToMap(data))
}

// buildSignStringFromMap 按富友规则将字段集合拼接为签名原文。
func buildSignStringFromMap(fields map[string]string) string {
	// 按 key 字典序排列
	keys := make([]string, 0, len(fields))
	for k := range fields {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	// 拼接
	pairs := make([]string, 0, len(keys))
	for _, k := range keys {
		pairs = append(pairs, k+"="+fields[k])
	}

	return strings.Join(pairs, "&")
}

// structToMap 通过反射提取结构体的 xml tag 和值。
// 排除 sign 字段和 reserved 开头字段，保留空值字段参与签名。
func structToMap(data interface{}) map[string]string {
	result := make(map[string]string)

	v := reflect.ValueOf(data)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	t := v.Type()

	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)

		// 排除 XMLName 字段（xml.Name 类型，仅用于指定根元素名称）
		if field.Name == "XMLName" {
			continue
		}

		value := v.Field(i).String()

		tag := field.Tag.Get("xml")
		if tag == "" || tag == "-" {
			continue
		}

		// 排除 sign 字段
		if tag == "sign" {
			continue
		}

		// 排除 reserved 开头字段
		if strings.HasPrefix(tag, "reserved") {
			continue
		}

		result[tag] = value
	}

	return result
}

// utf8ToGBK 将 UTF-8 字节转换为 GBK 编码
func utf8ToGBK(input []byte) ([]byte, error) {
	reader := transform.NewReader(strings.NewReader(string(input)), simplifiedchinese.GBK.NewEncoder())
	return io.ReadAll(reader)
}

// GBKToUTF8 将 GBK 字节转换为 UTF-8 编码
func GBKToUTF8(input []byte) ([]byte, error) {
	reader := transform.NewReader(strings.NewReader(string(input)), simplifiedchinese.GBK.NewDecoder())
	return io.ReadAll(reader)
}

// generateRandomStr 生成 32 字符随机字符串
func generateRandomStr() string {
	b := make([]byte, 16)
	_, _ = rand.Read(b)
	return fmt.Sprintf("%x", b)
}

var (
	serverIP     string
	serverIPOnce sync.Once
)

// GetServerIP 获取服务器非回环 IPv4 地址（进程生命周期内缓存）
func GetServerIP() string {
	serverIPOnce.Do(func() {
		addrs, _ := net.InterfaceAddrs()
		for _, addr := range addrs {
			if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil {
				serverIP = ipNet.IP.String()
				return
			}
		}
		serverIP = "127.0.0.1"
	})
	return serverIP
}

// parsePrivateKey 解析 Base64 编码的 RSA 私钥
// 支持多种格式: PEM(PKCS1/PKCS8) 和 DER(PKCS1/PKCS8)
func parsePrivateKey(base64Str string) (*rsa.PrivateKey, error) {
	decoded, err := base64.StdEncoding.DecodeString(base64Str)
	if err != nil {
		return nil, fmt.Errorf("Base64 解码失败: %w", err)
	}

	// 尝试 PEM 格式（Base64 解码后是 PEM 文本）
	if block, _ := pem.Decode(decoded); block != nil {
		return parseDERPrivateKey(block.Bytes)
	}

	// 尝试 DER 格式（Base64 解码后直接是 DER 字节）
	return parseDERPrivateKey(decoded)
}

// parseDERPrivateKey 从 DER 字节解析 RSA 私钥，先尝试 PKCS8 再尝试 PKCS1
func parseDERPrivateKey(derBytes []byte) (*rsa.PrivateKey, error) {
	// 先尝试 PKCS8
	if key, err := x509.ParsePKCS8PrivateKey(derBytes); err == nil {
		if rsaKey, ok := key.(*rsa.PrivateKey); ok {
			return rsaKey, nil
		}
		return nil, fmt.Errorf("PKCS8 解析结果不是 RSA 私钥")
	}

	// 再尝试 PKCS1
	if key, err := x509.ParsePKCS1PrivateKey(derBytes); err == nil {
		return key, nil
	}

	return nil, fmt.Errorf("无法解析私钥（已尝试 PKCS8 和 PKCS1 格式）")
}

// parsePublicKey 解析 Base64 编码的 RSA 公钥
// 支持 PEM 和 DER 格式
func parsePublicKey(base64Str string) (*rsa.PublicKey, error) {
	decoded, err := base64.StdEncoding.DecodeString(base64Str)
	if err != nil {
		return nil, fmt.Errorf("Base64 解码失败: %w", err)
	}

	// 尝试 PEM 格式
	if block, _ := pem.Decode(decoded); block != nil {
		return parseDERPublicKey(block.Bytes)
	}

	// 尝试 DER 格式
	return parseDERPublicKey(decoded)
}

// parseDERPublicKey 从 DER 字节解析 RSA 公钥
func parseDERPublicKey(derBytes []byte) (*rsa.PublicKey, error) {
	pub, err := x509.ParsePKIXPublicKey(derBytes)
	if err != nil {
		return nil, fmt.Errorf("PKIX 公钥解析失败: %w", err)
	}

	rsaPub, ok := pub.(*rsa.PublicKey)
	if !ok {
		return nil, fmt.Errorf("解析结果不是 RSA 公钥")
	}

	return rsaPub, nil
}

// rsaPublicKeyFingerprint 返回 RSA 公钥的 SHA256 指纹，避免日志输出完整密钥。
func rsaPublicKeyFingerprint(pub *rsa.PublicKey) string {
	derBytes, err := x509.MarshalPKIXPublicKey(pub)
	if err != nil {
		return ""
	}
	sum := sha256.Sum256(derBytes)
	return hex.EncodeToString(sum[:])
}

func rsaPublicKeysEqual(a, b *rsa.PublicKey) bool {
	if a == nil || b == nil {
		return false
	}
	return a.E == b.E && a.N.Cmp(b.N) == 0
}
