package handlers

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

	"sale/internal/models"
	"sale/internal/utils"

	"github.com/gin-gonic/gin"
)

type PaymentHandler struct{}

func NewPaymentHandler() *PaymentHandler {
	return &PaymentHandler{}
}

type BepUsdtCreateRequest struct {
	TradeType   string  `json:"trade_type,omitempty"`
	OrderID     string  `json:"order_id"`
	Amount      float64 `json:"amount"`
	NotifyURL   string  `json:"notify_url"`
	RedirectURL string  `json:"redirect_url,omitempty"`
	Signature   string  `json:"signature"`
}

func (h *PaymentHandler) CreatePayment(c *gin.Context) {
	userID := c.GetUint("user_id")
	orderID, _ := strconv.Atoi(c.Param("id"))

	var order models.Order
	if err := utils.DB.Preload("OrderItems").Where("id = ? AND user_id = ?", orderID, userID).First(&order).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
		return
	}

	if order.Status != models.OrderStatusPendingPayment {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Order is not pending payment"})
		return
	}

	if order.PaymentExpiresAt != nil && time.Now().After(*order.PaymentExpiresAt) {
		tx := utils.DB.Begin()
		tx.Model(&order).Update("status", models.OrderStatusCancelled)
		for _, item := range order.OrderItems {
			var inventory models.Inventory
			if err := tx.Where("product_id = ?", item.ProductID).First(&inventory).Error; err == nil {
				tx.Model(&inventory).UpdateColumn("quantity", inventory.Quantity+item.Quantity)
			}
		}
		tx.Commit()
		c.JSON(http.StatusBadRequest, gin.H{"error": "支付已超时，订单已取消"})
		return
	}

	var channels []models.PaymentChannel
	utils.DB.Where("is_enabled = ? AND type = ?", true, "bepusdt").Order("sort_order ASC").Find(&channels)
	if len(channels) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "No available payment channel"})
		return
	}

	channel := channels[0]
	config := parseChannelConfig(channel.Config)

	apiURL := strings.TrimSuffix(config["bepusdt_api_url"], "/")
	apiToken := config["bepusdt_api_token"]
	tradeType := config["bepusdt_trade_type"]
	if tradeType == "" {
		tradeType = "usdt.trc20"
	}

	scheme := "http"
	if c.Request.TLS != nil || c.GetHeader("X-Forwarded-Proto") == "https" {
		scheme = "https"
	}
	host := c.Request.Host
	baseURL := fmt.Sprintf("%s://%s", scheme, host)

	notifyURL := fmt.Sprintf("%s/api/payment/bepusdt/notify", baseURL)
	redirectURL := fmt.Sprintf("%s/orders/%d", baseURL, order.ID)

	params := map[string]string{
		"order_id":     strconv.Itoa(int(order.ID)),
		"amount":       fmt.Sprint(order.TotalAmount),
		"notify_url":   notifyURL,
		"redirect_url": redirectURL,
	}
	if tradeType != "" {
		params["trade_type"] = tradeType
	}

	signature := signBepUsdt(params, apiToken)

	log.Printf("[BepUsdt] signature params: %v", params)
	log.Printf("[BepUsdt] signature: %s, token: %s", signature, apiToken)

	reqBody := BepUsdtCreateRequest{
		TradeType:   tradeType,
		OrderID:     strconv.Itoa(int(order.ID)),
		Amount:      order.TotalAmount,
		NotifyURL:   notifyURL,
		RedirectURL: redirectURL,
		Signature:   signature,
	}

	body, _ := json.Marshal(reqBody)
	log.Printf("[BepUsdt] request body: %s", string(body))

	req, err := http.NewRequest("POST", apiURL+"/api/v1/order/create-transaction", strings.NewReader(string(body)))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
		return
	}
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to connect payment gateway"})
		return
	}
	defer resp.Body.Close()

	respBody, _ := io.ReadAll(resp.Body)
	log.Printf("[BepUsdt] response: %s", string(respBody))

	var result map[string]interface{}
	json.Unmarshal(respBody, &result)

	if statusCode, ok := result["status_code"].(float64); !ok || int(statusCode) != 200 {
		errMsg := "Payment creation failed"
		if msg, ok := result["message"].(string); ok {
			errMsg = msg
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": errMsg})
		return
	}

	data, ok := result["data"].(map[string]interface{})
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid response from payment gateway"})
		return
	}

	tradeID, _ := data["trade_id"].(string)
	token, _ := data["token"].(string)
	paymentURL, _ := data["payment_url"].(string)
	walletAddress, _ := data["address"].(string)

	var amount float64
	if v, ok := data["amount"].(float64); ok {
		amount = v
	} else if v, ok := data["amount"].(string); ok {
		amount, _ = strconv.ParseFloat(v, 64)
	}

	var actualAmount float64
	if v, ok := data["actual_amount"].(float64); ok {
		actualAmount = v
	} else if v, ok := data["actual_amount"].(string); ok {
		actualAmount, _ = strconv.ParseFloat(v, 64)
	}
	if actualAmount == 0 {
		actualAmount = amount
	}

	var expiration int64
	if v, ok := data["expiration_time"].(float64); ok {
		expiration = int64(v)
	}
	if expiration > 1e12 {
		expiration = expiration / 1000
	}

	var remainingSeconds int64
	var expiresAt *time.Time
	if expiration > 0 {
		remainingSeconds = expiration - time.Now().Unix()
		if remainingSeconds < 0 {
			remainingSeconds = 0
		}
		if remainingSeconds > 0 {
			t := time.Now().Add(time.Duration(remainingSeconds) * time.Second)
			expiresAt = &t
		}
	}

	utils.DB.Model(&order).Updates(map[string]interface{}{
		"payment_method":      "bepusdt",
		"payment_expires_at":  expiresAt,
	})

	paymentInfo := map[string]interface{}{
		"trade_id":        tradeID,
		"order_id":        order.ID,
		"amount":          amount,
		"actual_amount":   actualAmount,
		"token":           token,
		"payment_url":     paymentURL,
		"wallet_address":  walletAddress,
		"expiration_time": remainingSeconds,
		"trade_type":      tradeType,
		"channel_id":      channel.ID,
	}

	c.JSON(http.StatusOK, gin.H{"data": paymentInfo})
}

func (h *PaymentHandler) BepUsdtNotify(c *gin.Context) {
	body, _ := io.ReadAll(c.Request.Body)

	var params map[string]interface{}
	json.Unmarshal(body, &params)

	orderIDStr, _ := params["order_id"].(string)
	status, _ := params["status"].(float64)
	signature, _ := params["signature"].(string)

	orderID, err := strconv.Atoi(orderIDStr)
	if err != nil {
		c.String(http.StatusOK, "ok")
		return
	}

	var order models.Order
	if err := utils.DB.First(&order, orderID).Error; err != nil {
		c.String(http.StatusOK, "ok")
		return
	}

	var channels []models.PaymentChannel
	utils.DB.Where("type = ?", "bepusdt").Find(&channels)
	if len(channels) == 0 {
		c.String(http.StatusOK, "ok")
		return
	}

	channel := channels[0]
	config := parseChannelConfig(channel.Config)
	apiToken := config["bepusdt_api_token"]

	stringParams := make(map[string]string)
	for k, v := range params {
		if k != "signature" {
			stringParams[k] = fmt.Sprintf("%v", v)
		}
	}
	expectedSign := signBepUsdt(stringParams, apiToken)

	if signature != expectedSign {
		c.String(http.StatusOK, "ok")
		return
	}

	if int(status) == 2 {
		utils.DB.Model(&order).Update("status", models.OrderStatusPendingConfirm)
	} else if int(status) == 3 {
		utils.DB.Model(&order).Update("status", models.OrderStatusCancelled)
	}

	c.String(http.StatusOK, "ok")
}

func (h *PaymentHandler) CheckPaymentStatus(c *gin.Context) {
	userID := c.GetUint("user_id")
	orderID, _ := strconv.Atoi(c.Param("id"))

	var order models.Order
	if err := utils.DB.Where("id = ? AND user_id = ?", orderID, userID).First(&order).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"data": gin.H{
		"status": order.Status,
	}})
}

func signBepUsdt(params map[string]string, token string) string {
	keys := make([]string, 0, len(params))
	for k := range params {
		if params[k] != "" && k != "signature" && k != "sign_type" {
			keys = append(keys, k)
		}
	}
	sort.Strings(keys)

	var parts []string
	for _, k := range keys {
		parts = append(parts, fmt.Sprintf("%s=%s", k, params[k]))
	}
	str := strings.Join(parts, "&") + token

	hash := md5.Sum([]byte(str))
	return hex.EncodeToString(hash[:])
}

func parseChannelConfig(configStr string) map[string]string {
	result := make(map[string]string)
	if configStr == "" {
		return result
	}
	var raw map[string]interface{}
	if err := json.Unmarshal([]byte(configStr), &raw); err != nil {
		var rawStr map[string]string
		if err2 := json.Unmarshal([]byte(configStr), &rawStr); err2 == nil {
			return rawStr
		}
		log.Printf("[BepUsdt] failed to parse channel config: %v", err)
		return result
	}
	for k, v := range raw {
		if v == nil {
			continue
		}
		switch val := v.(type) {
		case string:
			result[k] = val
		case float64:
			if val == float64(int64(val)) {
				result[k] = strconv.FormatInt(int64(val), 10)
			} else {
				result[k] = strconv.FormatFloat(val, 'f', -1, 64)
			}
		case bool:
			result[k] = strconv.FormatBool(val)
		default:
			result[k] = fmt.Sprintf("%v", val)
		}
	}
	return result
}
