package mifapay

import (
	"bytes"
	"context"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"net/http"
	"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.MifaPayConfig
	httpClient *http.Client
	privateKey *rsa.PrivateKey
	publicKey  *rsa.PublicKey
}

type envelope struct {
	MerAccount string `json:"merAccount"`
	Data       string `json:"data"`
	Sign       string `json:"sign"`
}

func New(cfg config.MifaPayConfig) (*Client, error) {
	privateKey, err := parsePrivateKey(cfg.PrivateKey)
	if err != nil {
		return nil, err
	}
	publicKey, err := parsePublicKey(cfg.PlatformPublicKey)
	if err != nil {
		return nil, err
	}
	if cfg.HTTPTimeout <= 0 {
		cfg.HTTPTimeout = 10 * time.Second
	}
	return &Client{
		cfg:        cfg,
		httpClient: &http.Client{Timeout: cfg.HTTPTimeout},
		privateKey: privateKey,
		publicKey:  publicKey,
	}, nil
}

// CreateOrder 按 MiFaPay 统一 envelope 协议下单；业务层只消费 payUrl 和 mbOrderId。
func (c *Client) CreateOrder(ctx context.Context, req walletservice.MifaPayCreateOrderRequest) (walletservice.MifaPayCreateOrderResponse, error) {
	if c == nil || c.privateKey == nil {
		return walletservice.MifaPayCreateOrderResponse{}, xerr.New(xerr.Unavailable, "mifapay client is not configured")
	}
	payer := rechargePayer(req)
	data := map[string]any{
		"amount":       mifaPayProviderAmountValue(req.ProviderAmountMinor, req.CurrencyCode),
		"orderId":      req.OrderID,
		"payWay":       req.PayWay,
		"language":     firstNonEmpty(req.Language, "en"),
		"terminalType": "WEB",
		"productInfo":  productInfo(req),
		"payType":      req.PayType,
		"merNo":        c.cfg.MerNo,
		"countryCode":  req.CountryCode,
		"notifyUrl":    req.NotifyURL,
		"currency":     req.CurrencyCode,
		"time":         time.Now().Unix(),
		"returnUrl":    req.ReturnURL,
		"memberId":     strconv.FormatInt(req.TargetUserID, 10),
		"userIp":       req.ClientIP,
		"email":        payer.email,
		"firstName":    payer.firstName,
		"lastName":     payer.lastName,
		"phone":        payer.phone,
	}
	body, err := c.signedEnvelope(data)
	if err != nil {
		return walletservice.MifaPayCreateOrderResponse{}, err
	}
	httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.cfg.APIBaseURL+"/paygateway/mbpay/order/en_v2", bytes.NewReader(body))
	if err != nil {
		return walletservice.MifaPayCreateOrderResponse{}, err
	}
	httpReq.Header.Set("Content-Type", "application/json; charset=UTF-8")
	resp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return walletservice.MifaPayCreateOrderResponse{}, err
	}
	defer resp.Body.Close()
	raw, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20))
	if err != nil {
		return walletservice.MifaPayCreateOrderResponse{}, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return walletservice.MifaPayCreateOrderResponse{}, xerr.New(xerr.Unavailable, "mifapay order request failed")
	}
	var outer envelope
	var withCode struct {
		envelope
		Code string `json:"code"`
		Msg  string `json:"msg"`
	}
	if err := json.Unmarshal(raw, &withCode); err != nil {
		return walletservice.MifaPayCreateOrderResponse{}, err
	}
	outer = withCode.envelope
	if withCode.Code != "" && withCode.Code != "000000" {
		// MiFaPay 已接收请求但按网关规则拒单时，本服务还没有进入任何账务状态流转；这类失败是三方依赖语义，
		// 不能映射成业务 CONFLICT，否则 H5 只能看到“conflict”，排障也会误判成幂等或商品状态冲突。
		return walletservice.MifaPayCreateOrderResponse{}, mifapayGatewayRejectionError{
			cause:   xerr.New(xerr.Unavailable, "mifapay order rejected: "+withCode.Code+": "+withCode.Msg),
			rawJSON: string(raw),
		}
	}
	if err := c.verify(outer.Data, outer.Sign); err != nil {
		return walletservice.MifaPayCreateOrderResponse{}, err
	}
	var inner struct {
		OrderID   string `json:"orderId"`
		PayURL    string `json:"payUrl"`
		MBOrderID string `json:"mbOrderId"`
	}
	if err := json.Unmarshal([]byte(outer.Data), &inner); err != nil {
		return walletservice.MifaPayCreateOrderResponse{}, err
	}
	if strings.TrimSpace(inner.PayURL) == "" || strings.TrimSpace(inner.MBOrderID) == "" {
		return walletservice.MifaPayCreateOrderResponse{}, xerr.New(xerr.Unavailable, "mifapay order response is incomplete")
	}
	return walletservice.MifaPayCreateOrderResponse{
		OrderID:         inner.OrderID,
		ProviderOrderID: inner.MBOrderID,
		PayURL:          inner.PayURL,
		RawJSON:         string(raw),
	}, nil
}

// QueryOrder 主动查询 MiFaPay 订单状态；H5 返回页依赖它补偿异步回调延迟或回调丢失的窗口期。
func (c *Client) QueryOrder(ctx context.Context, req walletservice.MifaPayQueryOrderRequest) (walletservice.MifaPayQueryOrderResponse, error) {
	if c == nil || c.privateKey == nil {
		return walletservice.MifaPayQueryOrderResponse{}, xerr.New(xerr.Unavailable, "mifapay client is not configured")
	}
	data := map[string]any{"time": time.Now().Unix()}
	if providerOrderID := strings.TrimSpace(req.ProviderOrderID); providerOrderID != "" {
		data["mbOrderId"] = providerOrderID
	} else if orderID := strings.TrimSpace(req.OrderID); orderID != "" {
		data["orderId"] = orderID
		data["orderDate"] = mifaPayOrderDate(req.OrderCreatedAtMS)
	} else {
		return walletservice.MifaPayQueryOrderResponse{}, xerr.New(xerr.InvalidArgument, "mifapay query order id is required")
	}
	body, err := c.signedEnvelope(data)
	if err != nil {
		return walletservice.MifaPayQueryOrderResponse{}, err
	}
	httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.cfg.APIBaseURL+"/paygateway/mbpay/order/query/en_v2", bytes.NewReader(body))
	if err != nil {
		return walletservice.MifaPayQueryOrderResponse{}, err
	}
	httpReq.Header.Set("Content-Type", "application/json; charset=UTF-8")
	resp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return walletservice.MifaPayQueryOrderResponse{}, err
	}
	defer resp.Body.Close()
	raw, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20))
	if err != nil {
		return walletservice.MifaPayQueryOrderResponse{}, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return walletservice.MifaPayQueryOrderResponse{}, xerr.New(xerr.Unavailable, "mifapay order query request failed")
	}
	var outer envelope
	var withCode struct {
		envelope
		Code string `json:"code"`
		Msg  string `json:"msg"`
	}
	if err := json.Unmarshal(raw, &withCode); err != nil {
		return walletservice.MifaPayQueryOrderResponse{}, err
	}
	outer = withCode.envelope
	if withCode.Code != "" && withCode.Code != "000000" {
		return walletservice.MifaPayQueryOrderResponse{}, mifapayGatewayRejectionError{
			cause:   xerr.New(xerr.Unavailable, "mifapay order query rejected: "+withCode.Code+": "+withCode.Msg),
			rawJSON: string(raw),
		}
	}
	if err := c.verify(outer.Data, outer.Sign); err != nil {
		return walletservice.MifaPayQueryOrderResponse{}, err
	}
	var inner struct {
		Amount    string `json:"amount"`
		OrderID   string `json:"orderId"`
		PayType   string `json:"payType"`
		Status    string `json:"status"`
		Currency  string `json:"currency"`
		MBOrderID string `json:"mbOrderId"`
	}
	if err := json.Unmarshal([]byte(outer.Data), &inner); err != nil {
		return walletservice.MifaPayQueryOrderResponse{}, err
	}
	if strings.TrimSpace(inner.OrderID) == "" {
		return walletservice.MifaPayQueryOrderResponse{}, xerr.New(xerr.Unavailable, "mifapay order query response is incomplete")
	}
	return walletservice.MifaPayQueryOrderResponse{
		OrderID:         inner.OrderID,
		ProviderOrderID: inner.MBOrderID,
		Status:          inner.Status,
		Amount:          inner.Amount,
		Currency:        inner.Currency,
		PayType:         inner.PayType,
		RawJSON:         outer.Data,
	}, nil
}

// ParseNotification 验签 MiFaPay 回调并返回内层业务字段；验签失败不允许业务层入账。
func (c *Client) ParseNotification(notification ledger.MifaPayNotification) (walletservice.MifaPayNotifyData, error) {
	if c == nil || c.publicKey == nil {
		return walletservice.MifaPayNotifyData{}, xerr.New(xerr.Unavailable, "mifapay client is not configured")
	}
	if strings.TrimSpace(notification.MerAccount) != c.cfg.MerAccount {
		return walletservice.MifaPayNotifyData{}, xerr.New(xerr.PermissionDenied, "mifapay mer_account is invalid")
	}
	if err := c.verify(notification.Data, notification.Sign); err != nil {
		return walletservice.MifaPayNotifyData{}, err
	}
	var inner struct {
		Amount      string `json:"amount"`
		PayType     string `json:"payType"`
		OrderID     string `json:"orderId"`
		BankOrderNo string `json:"bankOrderNo"`
		OrderStatus string `json:"orderStatus"`
		Currency    string `json:"currency"`
		MBOrderID   string `json:"mbOrderId"`
		MemberID    string `json:"memberId"`
	}
	if err := json.Unmarshal([]byte(notification.Data), &inner); err != nil {
		return walletservice.MifaPayNotifyData{}, err
	}
	if strings.TrimSpace(inner.OrderID) == "" {
		return walletservice.MifaPayNotifyData{}, xerr.New(xerr.InvalidArgument, "mifapay callback order_id is required")
	}
	return walletservice.MifaPayNotifyData{
		OrderID:         inner.OrderID,
		ProviderOrderID: inner.MBOrderID,
		OrderStatus:     inner.OrderStatus,
		Amount:          inner.Amount,
		Currency:        inner.Currency,
		PayType:         inner.PayType,
		RawJSON:         notification.Data,
	}, nil
}

type mifapayGatewayRejectionError struct {
	cause   error
	rawJSON string
}

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

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

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

func (c *Client) signedEnvelope(data any) ([]byte, error) {
	dataJSON, err := json.Marshal(data)
	if err != nil {
		return nil, err
	}
	sign, err := c.sign(string(dataJSON))
	if err != nil {
		return nil, err
	}
	return json.Marshal(envelope{MerAccount: c.cfg.MerAccount, Data: string(dataJSON), Sign: sign})
}

func (c *Client) sign(data string) (string, error) {
	hash := sha256.Sum256([]byte(data))
	signature, err := rsa.SignPKCS1v15(rand.Reader, c.privateKey, crypto.SHA256, hash[:])
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(signature), nil
}

func (c *Client) verify(data string, sign string) error {
	if c.publicKey == nil {
		return nil
	}
	signature, err := base64.StdEncoding.DecodeString(strings.TrimSpace(sign))
	if err != nil {
		return err
	}
	hash := sha256.Sum256([]byte(data))
	if err := rsa.VerifyPKCS1v15(c.publicKey, crypto.SHA256, hash[:], signature); err != nil {
		return xerr.New(xerr.PermissionDenied, "mifapay signature is invalid")
	}
	return nil
}

func parsePrivateKey(value string) (*rsa.PrivateKey, error) {
	block := pemBlock(value, "PRIVATE KEY")
	key, err := x509.ParsePKCS8PrivateKey(block)
	if err != nil {
		return nil, err
	}
	privateKey, ok := key.(*rsa.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("mifapay private key is not rsa")
	}
	return privateKey, nil
}

func parsePublicKey(value string) (*rsa.PublicKey, error) {
	block := pemBlock(value, "PUBLIC KEY")
	key, err := x509.ParsePKIXPublicKey(block)
	if err != nil {
		return nil, err
	}
	publicKey, ok := key.(*rsa.PublicKey)
	if !ok {
		return nil, fmt.Errorf("mifapay public key is not rsa")
	}
	return publicKey, nil
}

func pemBlock(value string, blockType string) []byte {
	value = strings.TrimSpace(value)
	if decoded, _ := pem.Decode([]byte(value)); decoded != nil {
		return decoded.Bytes
	}
	clean := strings.NewReplacer("\n", "", "\r", "", " ", "").Replace(value)
	body, err := base64.StdEncoding.DecodeString(clean)
	if err == nil {
		return body
	}
	return []byte(value)
}

func productInfo(req walletservice.MifaPayCreateOrderRequest) string {
	body, _ := json.Marshal([]map[string]string{{
		"quantity":    "1",
		"price":       strconv.FormatInt(req.AmountMinor, 10),
		"sku":         req.OrderID,
		"productName": req.ProductName,
	}})
	return string(body)
}

func mifaPayProviderAmountValue(providerAmountMinor int64, currencyCode string) string {
	if strings.EqualFold(strings.TrimSpace(currencyCode), "INR") {
		// MiFaPay India treats amount as whole INR units while wallet snapshots keep local minor units;
		// converting at the client boundary keeps V5Pay and non-INR MiFaPay contracts unchanged.
		return strconv.FormatInt(providerAmountMinor/100, 10)
	}
	return strconv.FormatInt(providerAmountMinor, 10)
}

type payerIdentity struct {
	email     string
	firstName string
	lastName  string
	phone     string
}

func rechargePayer(req walletservice.MifaPayCreateOrderRequest) payerIdentity {
	account := strings.TrimSpace(req.PayerAccount)
	if account == "" {
		account = strconv.FormatInt(normalizedPayerUserID(req.TargetUserID), 10)
	}
	name := strings.TrimSpace(req.PayerName)
	if name == "" {
		name = account
	}
	email := strings.TrimSpace(req.PayerEmail)
	if email == "" {
		email = fmt.Sprintf("mifapay-payer-%d@haiyihy.com", normalizedPayerUserID(req.TargetUserID))
	}
	// H5 付款人参数必须来自 gateway 已解析出的目标用户资料：昵称同时写 firstName/lastName，账号写 phone，
	// 邮箱按“账号@app_code.com”生成。兜底只服务旧内部调用方，防止 MiFaPay 因空字段拒单，不代表用户资料事实。
	return payerIdentity{
		email:     email,
		firstName: name,
		lastName:  name,
		phone:     account,
	}
}

func normalizedPayerUserID(targetUserID int64) int64 {
	if targetUserID <= 0 {
		return 1
	}
	return targetUserID
}

func mifaPayOrderDate(createdAtMS int64) string {
	if createdAtMS <= 0 {
		return time.Now().UTC().Format("20060102")
	}
	return time.UnixMilli(createdAtMS).UTC().Format("20060102")
}

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