package middleware

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"xboard-go/internal/service"

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

const nodeAuthPayloadKey = "_node_auth_payload"

func NodeAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		token := nodeAuthValue(c, "token")
		nodeID := nodeAuthValue(c, "node_id")
		nodeType := service.NormalizeServerType(nodeAuthValue(c, "node_type"))

		if token == "" || nodeID == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"message": "missing node credentials"})
			c.Abort()
			return
		}

		if !service.IsValidServerType(nodeType) {
			c.JSON(http.StatusBadRequest, gin.H{"message": "invalid node type"})
			c.Abort()
			return
		}

		serverToken := service.MustGetString("server_token", "")
		if serverToken == "" {
			c.JSON(http.StatusInternalServerError, gin.H{"message": "server_token is not configured"})
			c.Abort()
			return
		}

		if token != serverToken {
			c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid server token"})
			c.Abort()
			return
		}

		node, err := service.FindServer(nodeID, nodeType)
		if err != nil {
			c.JSON(http.StatusNotFound, gin.H{"message": "server not found"})
			c.Abort()
			return
		}

		c.Set("node", node)
		c.Next()
	}
}

func nodeAuthValue(c *gin.Context, key string) string {
	if value := strings.TrimSpace(c.Query(key)); value != "" {
		return value
	}

	if value := strings.TrimSpace(c.PostForm(key)); value != "" {
		return value
	}

	switch key {
	case "token":
		if value := strings.TrimSpace(c.GetHeader("X-Server-Token")); value != "" {
			return value
		}
		if auth := strings.TrimSpace(c.GetHeader("Authorization")); len(auth) > 7 && strings.EqualFold(auth[:7], "Bearer ") {
			return strings.TrimSpace(auth[7:])
		}
	case "node_id":
		if value := strings.TrimSpace(c.GetHeader("X-Node-Id")); value != "" {
			return value
		}
	case "node_type":
		if value := strings.TrimSpace(c.GetHeader("X-Node-Type")); value != "" {
			return value
		}
	}

	payload := nodeAuthPayload(c)
	if payload == nil {
		return ""
	}

	return nodeAuthString(payload[key])
}

func nodeAuthPayload(c *gin.Context) map[string]any {
	if cached, ok := c.Get(nodeAuthPayloadKey); ok {
		if payload, ok := cached.(map[string]any); ok {
			return payload
		}
		return nil
	}

	if c.Request == nil || c.Request.Body == nil {
		c.Set(nodeAuthPayloadKey, map[string]any(nil))
		return nil
	}

	contentType := strings.ToLower(c.GetHeader("Content-Type"))
	if !strings.Contains(contentType, "application/json") {
		c.Set(nodeAuthPayloadKey, map[string]any(nil))
		return nil
	}

	body, err := io.ReadAll(c.Request.Body)
	if err != nil {
		c.Request.Body = io.NopCloser(bytes.NewBuffer(nil))
		c.Set(nodeAuthPayloadKey, map[string]any(nil))
		return nil
	}
	c.Request.Body = io.NopCloser(bytes.NewBuffer(body))

	if len(bytes.TrimSpace(body)) == 0 {
		c.Set(nodeAuthPayloadKey, map[string]any(nil))
		return nil
	}

	var payload map[string]any
	if err := json.Unmarshal(body, &payload); err != nil {
		c.Set(nodeAuthPayloadKey, map[string]any(nil))
		return nil
	}

	c.Set(nodeAuthPayloadKey, payload)
	return payload
}

func nodeAuthString(value any) string {
	switch typed := value.(type) {
	case string:
		return strings.TrimSpace(typed)
	case json.Number:
		return strings.TrimSpace(typed.String())
	default:
		if value == nil {
			return ""
		}
		text := strings.TrimSpace(fmt.Sprint(value))
		if text == "<nil>" {
			return ""
		}
		return text
	}
}
