package handler

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"
	"xboard-go/internal/database"
	"xboard-go/internal/model"
	"xboard-go/internal/service"

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

func NodeUser(c *gin.Context) {
	node := c.MustGet("node").(*model.Server)
	setNodeLastCheck(node)

	users, err := service.AvailableUsersForNode(node)
	if err != nil {
		Fail(c, 500, "failed to fetch users")
		return
	}

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

func NodeShadowsocksTidalabUser(c *gin.Context) {
	node := c.MustGet("node").(*model.Server)
	if node.Type != "shadowsocks" {
		Fail(c, http.StatusBadRequest, "server is not a shadowsocks node")
		return
	}
	setNodeLastCheck(node)

	users, err := service.AvailableUsersForNode(node)
	if err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch users")
		return
	}

	cipher := tidalabProtocolString(node.ProtocolSettings, "cipher")
	result := make([]gin.H, 0, len(users))
	for _, user := range users {
		result = append(result, gin.H{
			"id":     user.ID,
			"port":   node.ServerPort,
			"cipher": cipher,
			"secret": user.UUID,
		})
	}

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

func NodeTrojanTidalabUser(c *gin.Context) {
	node := c.MustGet("node").(*model.Server)
	if node.Type != "trojan" {
		Fail(c, http.StatusBadRequest, "server is not a trojan node")
		return
	}
	setNodeLastCheck(node)

	users, err := service.AvailableUsersForNode(node)
	if err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch users")
		return
	}

	result := make([]gin.H, 0, len(users))
	for _, user := range users {
		item := gin.H{
			"id":          user.ID,
			"trojan_user": gin.H{"password": user.UUID},
		}
		if user.SpeedLimit != nil {
			item["speed_limit"] = *user.SpeedLimit
		}
		if user.DeviceLimit != nil {
			item["device_limit"] = *user.DeviceLimit
		}
		result = append(result, item)
	}

	c.JSON(http.StatusOK, gin.H{
		"msg":  "ok",
		"data": result,
	})
}

type UniProxyConfigResponse struct {
	Protocol        string `json:"protocol"`
	ListenIP        string `json:"listen_ip"`
	ServerPort      int    `json:"server_port"`
	Network         any    `json:"network"`
	NetworkSettings any    `json:"networkSettings"`
	TLS             int    `json:"tls,omitempty"`
	Flow            any    `json:"flow,omitempty"`
	Cipher          any    `json:"cipher,omitempty"`
	ServerKey       any    `json:"server_key,omitempty"`
	Decryption      any    `json:"decryption,omitempty"`
	TLSSettings     any    `json:"tls_settings,omitempty"`
	Multiplex       any    `json:"multiplex,omitempty"`
	Routes          any    `json:"routes,omitempty"`
	CustomOutbounds any    `json:"custom_outbounds,omitempty"`
	CustomRoutes    any    `json:"custom_routes,omitempty"`
	CertConfig      any    `json:"cert_config,omitempty"`
	BaseConfig      any    `json:"base_config"`
}

func NodeConfig(c *gin.Context) {
	node := c.MustGet("node").(*model.Server)
	setNodeLastCheck(node)

	payload := service.BuildNodeConfigPayload(node)
	baseConfig := service.BuildNodeBaseConfigPayload()

	resp := UniProxyConfigResponse{
		Protocol:        node.Type,
		ListenIP:        "0.0.0.0",
		ServerPort:      node.ServerPort,
		Network:         payload["network"],
		NetworkSettings: payload["networkSettings"],
		TLS:             toIntDefault(payload["tls"]),
		Flow:            payload["flow"],
		Cipher:          payload["cipher"],
		ServerKey:       payload["server_key"],
		Decryption:      payload["decryption"],
		TLSSettings:     payload["tls_settings"],
		Multiplex:       payload["multiplex"],
		Routes:          payload["routes"],
		CustomOutbounds: payload["custom_outbounds"],
		CustomRoutes:    payload["custom_routes"],
		CertConfig:      payload["cert_config"],
		BaseConfig:      baseConfig,
	}

	c.JSON(http.StatusOK, resp)
}

func toIntDefault(val any) int {
	if val == nil {
		return 0
	}
	if i, ok := val.(int); ok {
		return i
	}
	return 0
}

func NodeTrojanTidalabConfig(c *gin.Context) {
	node := c.MustGet("node").(*model.Server)
	if node.Type != "trojan" {
		Fail(c, http.StatusBadRequest, "server is not a trojan node")
		return
	}
	setNodeLastCheck(node)

	localPort, err := strconv.Atoi(strings.TrimSpace(c.Query("local_port")))
	if err != nil || localPort <= 0 {
		Fail(c, http.StatusBadRequest, "local_port is required")
		return
	}

	serverName := tidalabProtocolString(node.ProtocolSettings, "server_name")
	if serverName == "" {
		serverName = strings.TrimSpace(node.Host)
	}
	if serverName == "" {
		serverName = "domain.com"
	}

	c.JSON(http.StatusOK, gin.H{
		"run_type":    "server",
		"local_addr":  "0.0.0.0",
		"local_port":  node.ServerPort,
		"remote_addr": "www.taobao.com",
		"remote_port": 80,
		"password":    []string{},
		"ssl": gin.H{
			"cert": "server.crt",
			"key":  "server.key",
			"sni":  serverName,
		},
		"api": gin.H{
			"enabled":  true,
			"api_addr": "127.0.0.1",
			"api_port": localPort,
		},
	})
}

func NodePush(c *gin.Context) {
	node := c.MustGet("node").(*model.Server)

	var payload map[string][]int64
	if err := c.ShouldBindJSON(&payload); err != nil {
		Fail(c, 400, "invalid payload")
		return
	}

	for userIDRaw, traffic := range payload {
		if len(traffic) != 2 {
			continue
		}
		userID, err := strconv.Atoi(userIDRaw)
		if err != nil {
			continue
		}
		service.ApplyTrafficDelta(userID, node, traffic[0], traffic[1])
	}

	setNodeLastPush(node, len(payload))
	Success(c, true)
}

func NodeTidalabSubmit(c *gin.Context) {
	node := c.MustGet("node").(*model.Server)
	if node.Type != "shadowsocks" && node.Type != "trojan" {
		Fail(c, http.StatusBadRequest, "server type is not supported by tidalab submit")
		return
	}

	var payload []struct {
		UserID int   `json:"user_id"`
		U      int64 `json:"u"`
		D      int64 `json:"d"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil {
		Fail(c, http.StatusBadRequest, "invalid payload")
		return
	}

	for _, item := range payload {
		if item.UserID <= 0 {
			continue
		}
		service.ApplyTrafficDelta(item.UserID, node, item.U, item.D)
	}

	setNodeLastPush(node, len(payload))
	c.JSON(http.StatusOK, gin.H{
		"ret": 1,
		"msg": "ok",
	})
}

func NodeAlive(c *gin.Context) {
	node := c.MustGet("node").(*model.Server)
	setNodeLastCheck(node)

	if c.Request.Method == http.MethodGet {
		Success(c, true)
		return
	}

	var payload map[string][]string
	if err := c.ShouldBindJSON(&payload); err != nil {
		if errors.Is(err, io.EOF) {
			Success(c, true)
			return
		}
		Fail(c, 400, "invalid payload")
		return
	}

	for userIDRaw, ips := range payload {
		userID, err := strconv.Atoi(userIDRaw)
		if err != nil {
			continue
		}
		_ = service.SetDevices(userID, node.ID, ips)
	}

	Success(c, true)
}

func NodeAliveList(c *gin.Context) {
	node := c.MustGet("node").(*model.Server)
	users, err := service.AvailableUsersForNode(node)
	if err != nil {
		Fail(c, 500, "failed to fetch users")
		return
	}

	userIDs := make([]int, 0, len(users))
	for _, user := range users {
		if user.DeviceLimit != nil && *user.DeviceLimit > 0 {
			userIDs = append(userIDs, user.ID)
		}
	}

	devices := service.GetUsersDevices(userIDs)
	alive := make(map[string][]string, len(devices))
	for userID, ips := range devices {
		alive[strconv.Itoa(userID)] = ips
	}

	Success(c, gin.H{"alive": alive})
}

func NodeStatus(c *gin.Context) {
	node := c.MustGet("node").(*model.Server)
	var status map[string]any
	if err := c.ShouldBindJSON(&status); err != nil {
		Fail(c, 400, "invalid payload")
		return
	}

	cacheTime := time.Duration(maxInt(300, service.MustGetInt("server_push_interval", 60)*3)) * time.Second
	_ = database.CacheSet(nodeLoadStatusKey(node), gin.H{
		"cpu":           status["cpu"],
		"mem":           status["mem"],
		"swap":          status["swap"],
		"disk":          status["disk"],
		"kernel_status": status["kernel_status"],
		"updated_at":    time.Now().Unix(),
	}, cacheTime)
	_ = database.CacheSet(nodeLastLoadKey(node), time.Now().Unix(), cacheTime)

	c.JSON(http.StatusOK, gin.H{"data": true, "code": 0, "message": "success"})
}

func NodeHandshake(c *gin.Context) {
	websocket := gin.H{"enabled": false}
	if service.MustGetBool("server_ws_enable", true) {
		wsURL := strings.TrimSpace(service.MustGetString("server_ws_url", ""))
		if wsURL == "" {
			scheme := "ws"
			if c.Request.TLS != nil {
				scheme = "wss"
			}
			wsURL = fmt.Sprintf("%s://%s:8076", scheme, c.Request.Host)
		}
		websocket = gin.H{
			"enabled": true,
			"ws_url":  strings.TrimRight(wsURL, "/"),
		}
	}
	Success(c, gin.H{"websocket": websocket})
}

func NodeReport(c *gin.Context) {
	node := c.MustGet("node").(*model.Server)
	setNodeLastCheck(node)

	var payload struct {
		Traffic map[string][]int64  `json:"traffic"`
		Alive   map[string][]string `json:"alive"`
		Online  map[string]int      `json:"online"`
		Status  map[string]any      `json:"status"`
		Metrics map[string]any      `json:"metrics"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil {
		Fail(c, 400, "invalid payload")
		return
	}

	if len(payload.Traffic) > 0 {
		for userIDRaw, traffic := range payload.Traffic {
			if len(traffic) != 2 {
				continue
			}
			userID, err := strconv.Atoi(userIDRaw)
			if err != nil {
				continue
			}
			service.ApplyTrafficDelta(userID, node, traffic[0], traffic[1])
		}
		setNodeLastPush(node, len(payload.Traffic))
	}

	if len(payload.Alive) > 0 {
		for userIDRaw, ips := range payload.Alive {
			userID, err := strconv.Atoi(userIDRaw)
			if err != nil {
				continue
			}
			_ = service.SetDevices(userID, node.ID, ips)
		}
	}

	if len(payload.Online) > 0 {
		cacheTime := time.Duration(maxInt(300, service.MustGetInt("server_push_interval", 60)*3)) * time.Second
		for userIDRaw, conn := range payload.Online {
			key := fmt.Sprintf("USER_ONLINE_CONN_%s_%d_%s", strings.ToUpper(node.Type), node.ID, userIDRaw)
			_ = database.CacheSet(key, conn, cacheTime)
		}
	}

	if len(payload.Status) > 0 {
		cacheTime := time.Duration(maxInt(300, service.MustGetInt("server_push_interval", 60)*3)) * time.Second
		_ = database.CacheSet(nodeLoadStatusKey(node), gin.H{
			"cpu":           payload.Status["cpu"],
			"mem":           payload.Status["mem"],
			"swap":          payload.Status["swap"],
			"disk":          payload.Status["disk"],
			"kernel_status": payload.Status["kernel_status"],
			"updated_at":    time.Now().Unix(),
		}, cacheTime)
		_ = database.CacheSet(nodeLastLoadKey(node), time.Now().Unix(), cacheTime)
	}

	if len(payload.Metrics) > 0 {
		cacheTime := time.Duration(maxInt(300, service.MustGetInt("server_push_interval", 60)*3)) * time.Second
		payload.Metrics["updated_at"] = time.Now().Unix()
		_ = database.CacheSet(nodeMetricsKey(node), payload.Metrics, cacheTime)
	}

	Success(c, true)
}

func setNodeLastCheck(node *model.Server) {
	_ = database.CacheSet(nodeLastCheckKey(node), time.Now().Unix(), time.Hour)
}

func setNodeLastPush(node *model.Server, onlineUsers int) {
	_ = database.CacheSet(nodeOnlineKey(node), onlineUsers, time.Hour)
	_ = database.CacheSet(nodeLastPushKey(node), time.Now().Unix(), time.Hour)
}

func nodeLastCheckKey(node *model.Server) string {
	return fmt.Sprintf("SERVER_%s_LAST_CHECK_AT_%d", strings.ToUpper(node.Type), node.ID)
}

func nodeLastPushKey(node *model.Server) string {
	return fmt.Sprintf("SERVER_%s_LAST_PUSH_AT_%d", strings.ToUpper(node.Type), node.ID)
}

func nodeOnlineKey(node *model.Server) string {
	return fmt.Sprintf("SERVER_%s_ONLINE_USER_%d", strings.ToUpper(node.Type), node.ID)
}

func nodeLoadStatusKey(node *model.Server) string {
	return fmt.Sprintf("SERVER_%s_LOAD_STATUS_%d", strings.ToUpper(node.Type), node.ID)
}

func nodeLastLoadKey(node *model.Server) string {
	return fmt.Sprintf("SERVER_%s_LAST_LOAD_AT_%d", strings.ToUpper(node.Type), node.ID)
}

func nodeMetricsKey(node *model.Server) string {
	return fmt.Sprintf("SERVER_%s_METRICS_%d", strings.ToUpper(node.Type), node.ID)
}

func maxInt(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func tidalabProtocolString(raw *string, key string) string {
	if raw == nil || strings.TrimSpace(*raw) == "" {
		return ""
	}

	decoded := map[string]any{}
	if err := json.Unmarshal([]byte(*raw), &decoded); err != nil {
		return ""
	}

	value, _ := decoded[key].(string)
	return strings.TrimSpace(value)
}
