package protocol

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/url"
	"strings"
	"xboard-go/internal/model"
	"xboard-go/internal/service"
)

func GenerateGeneralLinks(servers []model.Server, user model.User) string {
	var links []string
	for _, s := range servers {
		conf := service.BuildNodeConfig(&s)
		password := service.GenerateServerPassword(&s, &user)
		link := BuildLink(conf, password)
		if link != "" {
			links = append(links, link)
		}
	}
	return strings.Join(links, "\n")
}

func BuildLink(c service.NodeServerConfig, password string) string {
	switch c.Protocol {
	case "shadowsocks":
		return buildShadowsocks(c, password)
	case "vmess":
		return buildVmess(c, password)
	case "vless":
		return buildVless(c, password)
	case "trojan":
		return buildTrojan(c, password)
	case "hysteria", "hysteria2":
		return buildHysteria(c, password)
	case "tuic":
		return buildTuic(c, password)
	case "anytls":
		return buildAnyTLS(c, password)
	case "socks":
		return buildSocks(c, password)
	case "http":
		return buildHttp(c, password)
	}
	return ""
}

func buildShadowsocks(c service.NodeServerConfig, password string) string {
	cipher := toString(c.Cipher)
	userInfo := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", cipher, password)))
	userInfo = strings.TrimRight(strings.ReplaceAll(strings.ReplaceAll(userInfo, "+", "-"), "/", "_"), "=")

	link := fmt.Sprintf("ss://%s@%s:%d", userInfo, wrapIPv6(c.RawHost), c.Port)
	params := url.Values{}
	if plugin := toString(c.Plugin); plugin != "" {
		opts := toString(c.PluginOpts)
		params.Set("plugin", plugin+";"+opts)
	}
	if q := params.Encode(); q != "" {
		link += "/?" + q
	}
	return link + "#" + url.PathEscape(c.Name)
}

func buildVmess(c service.NodeServerConfig, password string) string {
	m := map[string]any{
		"v":    "2",
		"ps":   c.Name,
		"add":  c.RawHost,
		"port": fmt.Sprintf("%d", c.Port),
		"id":   password,
		"aid":  "0",
		"net":  toString(c.Network),
		"type": "none",
		"host": "",
		"path": "",
		"tls":  "",
	}
	if c.Tls != nil && toInt(c.Tls) > 0 {
		m["tls"] = "tls"
	}

	if settings, ok := c.NetworkSettings.(map[string]any); ok {
		switch toString(c.Network) {
		case "ws":
			m["path"] = toString(settings["path"])
			if headers, ok := settings["headers"].(map[string]any); ok {
				m["host"] = toString(headers["Host"])
			}
		case "grpc":
			m["path"] = toString(settings["serviceName"])
		case "h2":
			m["path"] = toString(settings["path"])
			m["host"] = toString(settings["host"])
		}
	}

	if tlsSettings, ok := c.TlsSettings.(map[string]any); ok {
		m["sni"] = toString(tlsSettings["server_name"])
	}

	b, _ := json.Marshal(m)
	return "vmess://" + base64.StdEncoding.EncodeToString(b)
}

func buildVless(c service.NodeServerConfig, password string) string {
	params := url.Values{}
	params.Set("mode", "multi")
	params.Set("encryption", "none")
	if c.Flow != nil {
		params.Set("flow", toString(c.Flow))
	}

	security := "none"
	switch toInt(c.Tls) {
	case 1:
		security = "tls"
		if fp := tlsFingerprint(c.UTLS); fp != "" {
			params.Set("fp", fp)
		}
		if tlsSettings, ok := c.TlsSettings.(map[string]any); ok {
			params.Set("sni", toString(tlsSettings["server_name"]))
			if truthy(tlsSettings["allow_insecure"]) {
				params.Set("allowInsecure", "1")
			}
		}
	case 2:
		security = "reality"
		if fp := tlsFingerprint(c.UTLS); fp != "" {
			params.Set("fp", fp)
		}
		if tlsSettings, ok := c.TlsSettings.(map[string]any); ok {
			params.Set("pbk", toString(tlsSettings["public_key"]))
			params.Set("sid", toString(tlsSettings["short_id"]))
			params.Set("sni", toString(tlsSettings["server_name"]))
			params.Set("servername", toString(tlsSettings["server_name"]))
			params.Set("spx", "/")
		}
	}
	params.Set("security", security)
	params.Set("type", toString(c.Network))

	if settings, ok := c.NetworkSettings.(map[string]any); ok {
		switch toString(c.Network) {
		case "ws":
			params.Set("path", toString(settings["path"]))
			if headers, ok := settings["headers"].(map[string]any); ok {
				params.Set("host", toString(headers["Host"]))
			}
		case "grpc":
			params.Set("serviceName", toString(settings["serviceName"]))
		case "h2":
			params.Set("type", "http")
			params.Set("path", toString(settings["path"]))
			if host := joinConfigValue(settings["host"]); host != "" {
				params.Set("host", host)
			}
		case "httpupgrade":
			params.Set("path", toString(settings["path"]))
			host := toString(settings["host"])
			if host == "" {
				host = c.RawHost
			}
			params.Set("host", host)
		}
	}

	return fmt.Sprintf("vless://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
}

func buildTrojan(c service.NodeServerConfig, password string) string {
	params := url.Values{}
	security := "tls"
	if toInt(c.Tls) == 2 {
		security = "reality"
		if tlsSettings, ok := c.TlsSettings.(map[string]any); ok {
			params.Set("pbk", toString(tlsSettings["public_key"]))
			params.Set("sid", toString(tlsSettings["short_id"]))
			params.Set("sni", toString(tlsSettings["server_name"]))
		}
	} else {
		if tlsSettings, ok := c.TlsSettings.(map[string]any); ok {
			params.Set("sni", toString(tlsSettings["server_name"]))
		}
	}
	params.Set("security", security)

	if settings, ok := c.NetworkSettings.(map[string]any); ok {
		switch toString(c.Network) {
		case "ws":
			params.Set("type", "ws")
			params.Set("path", toString(settings["path"]))
		case "grpc":
			params.Set("type", "grpc")
			params.Set("serviceName", toString(settings["serviceName"]))
		}
	}

	return fmt.Sprintf("trojan://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
}

func buildHysteria(c service.NodeServerConfig, password string) string {
	params := url.Values{}
	params.Set("sni", toString(c.ServerName))

	if c.Protocol == "hysteria2" || toInt(c.Version) == 2 {
		if obfs := toString(c.Obfs); obfs != "" {
			params.Set("obfs", "salamander")
			params.Set("obfs-password", toString(c.ObfsPassword))
		}
		if c.Ports != "" {
			params.Set("mport", c.Ports)
		}
		return fmt.Sprintf("hysteria2://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
	}

	params.Set("protocol", "udp")
	params.Set("auth", password)
	if c.UpMbps != nil {
		params.Set("upmbps", fmt.Sprintf("%v", c.UpMbps))
	}
	if c.DownMbps != nil {
		params.Set("downmbps", fmt.Sprintf("%v", c.DownMbps))
	}

	return fmt.Sprintf("hysteria://%s:%d?%s#%s", wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
}

func buildTuic(c service.NodeServerConfig, password string) string {
	params := url.Values{}
	params.Set("sni", toString(c.ServerName))
	params.Set("congestion_control", toString(c.CongestionControl))
	params.Set("udp-relay-mode", "native")

	return fmt.Sprintf("tuic://%s:%s@%s:%d?%s#%s", password, password, wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
}

func buildAnyTLS(c service.NodeServerConfig, password string) string {
	params := url.Values{}
	params.Set("sni", toString(c.ServerName))
	return fmt.Sprintf("anytls://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
}

func buildSocks(c service.NodeServerConfig, password string) string {
	auth := base64.StdEncoding.EncodeToString([]byte(password + ":" + password))
	return fmt.Sprintf("socks://%s@%s:%d#%s", auth, wrapIPv6(c.RawHost), c.Port, url.PathEscape(c.Name))
}

func buildHttp(c service.NodeServerConfig, password string) string {
	auth := base64.StdEncoding.EncodeToString([]byte(password + ":" + password))
	link := fmt.Sprintf("http://%s@%s:%d", auth, wrapIPv6(c.RawHost), c.Port)
	if toInt(c.Tls) > 0 {
		params := url.Values{}
		params.Set("security", "tls")
		link += "?" + params.Encode()
	}
	return link + "#" + url.PathEscape(c.Name)
}

func wrapIPv6(host string) string {
	if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") {
		return "[" + host + "]"
	}
	return host
}

func tlsFingerprint(value any) string {
	settings, ok := value.(map[string]any)
	if !ok || !truthy(settings["enabled"]) {
		return ""
	}
	return toString(settings["fingerprint"])
}

func joinConfigValue(value any) string {
	switch typed := value.(type) {
	case string:
		return typed
	case []string:
		return strings.Join(typed, ",")
	case []any:
		result := make([]string, 0, len(typed))
		for _, item := range typed {
			text := toString(item)
			if text != "" {
				result = append(result, text)
			}
		}
		return strings.Join(result, ",")
	default:
		return ""
	}
}

func toString(v any) string {
	if s, ok := v.(string); ok {
		return s
	}
	if v == nil {
		return ""
	}
	return fmt.Sprintf("%v", v)
}

func toInt(v any) int {
	switch typed := v.(type) {
	case int:
		return typed
	case int64:
		return int(typed)
	case float64:
		return int(typed)
	}
	return 0
}

func truthy(v any) bool {
	switch typed := v.(type) {
	case bool:
		return typed
	case int:
		return typed != 0
	case int64:
		return typed != 0
	case float64:
		return typed != 0
	case string:
		switch strings.ToLower(strings.TrimSpace(typed)) {
		case "1", "true", "yes", "on":
			return true
		}
	}
	return false
}
