package api

import (
	"crypto/sha256"
	"encoding/hex"
	"log/slog"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"account/internal/store"
	"account/internal/xrayconfig"
)

type syncConfigAckRequest struct {
	Version   int64  `json:"version"`
	DeviceID  string `json:"device_id"`
	AppliedAt string `json:"applied_at"`
}

func (h *handler) syncConfigSnapshot(c *gin.Context) {
	h.respondSyncConfigSnapshot(c)
}

func (h *handler) syncConfig(c *gin.Context) {
	// Backward-compatible endpoint: old clients call POST /api/auth/config/sync.
	h.respondSyncConfigSnapshot(c)
}

func (h *handler) respondSyncConfigSnapshot(c *gin.Context) {
	user, ok := h.requireAuthenticatedUser(c)
	if !ok {
		return
	}

	sinceVersion := int64(0)
	if raw := strings.TrimSpace(c.Query("since_version")); raw != "" {
		v, err := strconv.ParseInt(raw, 10, 64)
		if err != nil || v < 0 {
			respondError(c, http.StatusBadRequest, "invalid_since_version", "since_version must be a non-negative integer")
			return
		}
		sinceVersion = v
	}

	version := deriveSyncVersion(user)
	updatedAt := time.Now().UTC()
	if !user.UpdatedAt.IsZero() {
		updatedAt = user.UpdatedAt.UTC()
	}

	changed := sinceVersion < version
	renderedJSON := ""
	digest := ""
	warnings := []string{}
	if changed {
		var err error
		renderedJSON, digest, warnings, err = h.renderUserXrayConfig(user)
		if err != nil {
			slog.Warn(
				"desktop sync config render failed; continuing with node metadata only",
				"user_id", strings.TrimSpace(user.ID),
				"user_email", strings.TrimSpace(user.Email),
				"error", err,
			)
			renderedJSON = ""
			digest = ""
			warnings = append(warnings, "rendered xray config unavailable; falling back to node metadata")
		}
	}

	profiles := []gin.H{}
	nodes := []gin.H{}
	if changed {
		proxyUUID := strings.TrimSpace(user.ProxyUUID)
		if proxyUUID == "" {
			proxyUUID = strings.TrimSpace(user.ID)
		}

		// Collect node hosts from registered agents + publicURL fallback.
		registeredHosts, registeredNames := registeredNodeMetadata(h.agentStatusReader)
		hosts := parseProxyNodeHosts(h.publicURL, registeredHosts)

		xhttpPath := envOrDefault("XRAY_XHTTP_PATH", defaultXHTTPPath)
		xhttpMode := envOrDefault("XRAY_XHTTP_MODE", defaultXHTTPMode)
		xhttpScheme := xrayconfig.VLESSXHTTPScheme()

		for _, host := range hosts {
			nodeName := resolveNodeName(host, registeredNames)
			countryCode := countryCodeForHost(host)
			vlessURI := renderVLESSURIScheme(xhttpScheme, map[string]string{
				"UUID":   proxyUUID,
				"DOMAIN": host,
				"NODE":   host,
				"PATH":   url.QueryEscape(xhttpPath),
				"MODE":   url.QueryEscape(xhttpMode),
				"SNI":    host,
				"FP":     defaultTLSFP,
				"TAG":    url.QueryEscape(nodeName),
			})

			profiles = append(profiles, gin.H{
				"id":           strings.TrimSpace(user.ID),
				"remark":       nodeName,
				"address":      host,
				"port":         443,
				"uuid":         proxyUUID,
				"flow":         "",
				"transport":    "xhttp",
				"security":     "tls",
				"source":       "server",
				"country_code": countryCode,
				"vless_uri":    vlessURI,
			})
			nodes = append(nodes, gin.H{
				"id":           strings.TrimSpace(user.ID),
				"name":         nodeName,
				"protocol":     "vless",
				"transport":    "xhttp",
				"security":     "tls",
				"address":      host,
				"port":         443,
				"uuid":         proxyUUID,
				"flow":         "",
				"source":       "server",
				"country_code": countryCode,
				"updated_at":   updatedAt,
				"vless_uri":    vlessURI,
			})
		}
	}

	c.JSON(http.StatusOK, gin.H{
		"schema_version": 1,
		"changed":        changed,
		"version":        version,
		"updated_at":     updatedAt,
		"profiles":       profiles,
		"nodes":          nodes,
		"rendered_json":  renderedJSON,
		"routes":         []gin.H{},
		"dns": gin.H{
			"mode":    "secure_tunnel",
			"servers": []string{},
		},
		"meta": gin.H{
			"digest":   digest,
			"warnings": warnings,
		},
		"digest":   digest,
		"warnings": warnings,
	})
}

func (h *handler) syncConfigAck(c *gin.Context) {
	user, ok := h.requireAuthenticatedUser(c)
	if !ok {
		return
	}

	var req syncConfigAckRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		respondError(c, http.StatusBadRequest, "invalid_request", "invalid request payload")
		return
	}

	if req.Version <= 0 {
		respondError(c, http.StatusBadRequest, "invalid_version", "version must be positive")
		return
	}
	if strings.TrimSpace(req.DeviceID) == "" {
		respondError(c, http.StatusBadRequest, "device_id_required", "device_id is required")
		return
	}
	if strings.TrimSpace(req.AppliedAt) == "" {
		respondError(c, http.StatusBadRequest, "applied_at_required", "applied_at is required")
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"acked":       true,
		"version":     req.Version,
		"device_id":   strings.TrimSpace(req.DeviceID),
		"user_id":     strings.TrimSpace(user.ID),
		"received_at": time.Now().UTC(),
	})
}

func deriveSyncVersion(user *store.User) int64 {
	if user == nil {
		return time.Now().UTC().Unix()
	}
	if !user.UpdatedAt.IsZero() {
		return user.UpdatedAt.UTC().Unix()
	}
	if !user.CreatedAt.IsZero() {
		return user.CreatedAt.UTC().Unix()
	}
	return time.Now().UTC().Unix()
}

func (h *handler) renderUserXrayConfig(user *store.User) (string, string, []string, error) {
	if h.xrayConfigRenderer != nil {
		return h.xrayConfigRenderer(user)
	}

	domain := extractHostFromPublicURL(h.publicURL)
	if domain == "" {
		domain = "accounts.svc.plus"
	}

	clientID := strings.TrimSpace(user.ProxyUUID)
	if clientID == "" {
		clientID = strings.TrimSpace(user.ID)
	}
	clients := []xrayconfig.Client{{
		ID:    clientID,
		Email: strings.TrimSpace(user.Email),
		Flow:  xrayconfig.DefaultFlow,
	}}

	gen := xrayconfig.Generator{
		Definition: xrayconfig.TCPDefinition(),
		Domain:     domain,
	}
	buf, err := gen.Render(clients)
	if err != nil {
		return "", "", nil, err
	}
	sum := sha256.Sum256(buf)
	return string(buf), hex.EncodeToString(sum[:]), []string{}, nil
}

func extractHostFromPublicURL(raw string) string {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return ""
	}
	u, err := url.Parse(trimmed)
	if err != nil {
		return ""
	}
	return strings.TrimSpace(u.Hostname())
}
