package protocol

import (
	"encoding/json"
	"xboard-go/internal/model"
	"xboard-go/internal/service"
)

type SingBoxConfig struct {
	Outbounds []map[string]interface{} `json:"outbounds"`
}

func GenerateSingBox(servers []model.Server, user model.User) (string, error) {
	template := service.GetSubscribeTemplate("singbox")
	if template == "" {
		return generateSingBoxFallback(servers, user)
	}

	var config map[string]any
	if err := json.Unmarshal([]byte(template), &config); err != nil {
		return generateSingBoxFallback(servers, user)
	}

	outbounds := make([]any, 0, len(servers))
	proxyTags := make([]string, 0, len(servers))
	for _, s := range servers {
		conf := service.BuildNodeConfig(&s)
		password := service.GenerateServerPassword(&s, &user)
		outbound := buildSingBoxOutbound(conf, password)
		if outbound == nil {
			continue
		}

		outbounds = append(outbounds, outbound)
		proxyTags = append(proxyTags, conf.Name)
	}

	existingOutbounds := anySlice(config["outbounds"])
	for index, item := range existingOutbounds {
		outbound, ok := item.(map[string]any)
		if !ok {
			continue
		}

		outboundType, _ := outbound["type"].(string)
		if outboundType != "selector" && outboundType != "urltest" {
			continue
		}

		outbound["outbounds"] = appendUniqueAny(anySlice(outbound["outbounds"]), stringsToAny(proxyTags)...)
		existingOutbounds[index] = outbound
	}

	config["outbounds"] = append(existingOutbounds, outbounds...)

	data, err := json.MarshalIndent(config, "", "  ")
	if err != nil {
		return "", err
	}
	return string(data), nil
}

func buildSingBoxOutbound(conf service.NodeServerConfig, password string) map[string]any {
	outbound := map[string]any{
		"tag":         conf.Name,
		"server":      conf.RawHost,
		"server_port": conf.Port,
	}

	switch conf.Protocol {
	case "shadowsocks":
		outbound["type"] = "shadowsocks"
		outbound["method"] = conf.Cipher
		outbound["password"] = password
	case "vmess":
		outbound["type"] = "vmess"
		outbound["uuid"] = password
		outbound["security"] = "auto"
	case "vless":
		outbound["type"] = "vless"
		outbound["uuid"] = password
		outbound["packet_encoding"] = "xudp"
		if flow := singBoxString(conf.Flow); flow != "" {
			outbound["flow"] = flow
		}
		if tls := buildSingBoxTLS(conf); tls != nil {
			outbound["tls"] = tls
		}
		if transport := buildSingBoxTransport(conf); transport != nil {
			outbound["transport"] = transport
		}
		if multiplex := buildSingBoxMultiplex(conf.Multiplex); multiplex != nil {
			outbound["multiplex"] = multiplex
		}
	case "trojan":
		outbound["type"] = "trojan"
		outbound["password"] = password
	default:
		return nil
	}

	return outbound
}

func generateSingBoxFallback(servers []model.Server, user model.User) (string, error) {
	outbounds := []map[string]interface{}{}
	proxyTags := []string{}

	for _, s := range servers {
		conf := service.BuildNodeConfig(&s)
		password := service.GenerateServerPassword(&s, &user)
		outbound := map[string]interface{}{
			"tag":         conf.Name,
			"server":      conf.RawHost,
			"server_port": conf.Port,
		}

		switch conf.Protocol {
		case "shadowsocks":
			outbound["type"] = "shadowsocks"
			outbound["method"] = conf.Cipher
			outbound["password"] = password
		case "vmess":
			outbound["type"] = "vmess"
			outbound["uuid"] = password
			outbound["security"] = "auto"
		case "vless":
			outbound["type"] = "vless"
			outbound["uuid"] = password
			outbound["packet_encoding"] = "xudp"
			if flow := singBoxString(conf.Flow); flow != "" {
				outbound["flow"] = flow
			}
			if tls := buildSingBoxTLS(conf); tls != nil {
				outbound["tls"] = tls
			}
			if transport := buildSingBoxTransport(conf); transport != nil {
				outbound["transport"] = transport
			}
			if multiplex := buildSingBoxMultiplex(conf.Multiplex); multiplex != nil {
				outbound["multiplex"] = multiplex
			}
		case "trojan":
			outbound["type"] = "trojan"
			outbound["password"] = password
		default:
			continue
		}

		outbounds = append(outbounds, outbound)
		proxyTags = append(proxyTags, conf.Name)
	}

	selector := map[string]interface{}{
		"type":      "selector",
		"tag":       "Proxy",
		"outbounds": proxyTags,
	}
	outbounds = append(outbounds, selector)

	config := SingBoxConfig{
		Outbounds: outbounds,
	}

	data, err := json.MarshalIndent(config, "", "  ")
	return string(data), err
}

func buildSingBoxTLS(conf service.NodeServerConfig) map[string]any {
	utlsConfig := buildSingBoxUTLS(conf.UTLS)
	switch singBoxInt(conf.Tls) {
	case 1:
		tls := map[string]any{
			"enabled": true,
		}
		if tlsSettings, ok := conf.TlsSettings.(map[string]any); ok {
			tls["insecure"] = singBoxBool(tlsSettings["allow_insecure"])
			if serverName := singBoxString(tlsSettings["server_name"]); serverName != "" {
				tls["server_name"] = serverName
			}
		}
		if utlsConfig != nil {
			tls["utls"] = utlsConfig
		}
		return tls
	case 2:
		tls := map[string]any{
			"enabled": true,
		}
		if tlsSettings, ok := conf.TlsSettings.(map[string]any); ok {
			tls["insecure"] = singBoxBool(tlsSettings["allow_insecure"])
			if serverName := singBoxString(tlsSettings["server_name"]); serverName != "" {
				tls["server_name"] = serverName
			}
			tls["reality"] = map[string]any{
				"enabled":    true,
				"public_key": singBoxString(tlsSettings["public_key"]),
				"short_id":   singBoxString(tlsSettings["short_id"]),
			}
		}
		if utlsConfig != nil {
			tls["utls"] = utlsConfig
		}
		return tls
	default:
		return nil
	}
}

func buildSingBoxTransport(conf service.NodeServerConfig) map[string]any {
	network := singBoxString(conf.Network)
	settings, _ := conf.NetworkSettings.(map[string]any)
	switch network {
	case "ws":
		transport := map[string]any{
			"type": "ws",
		}
		if settings != nil {
			if path := singBoxString(settings["path"]); path != "" {
				transport["path"] = path
			}
			if headers, ok := settings["headers"].(map[string]any); ok {
				if host := singBoxString(headers["Host"]); host != "" {
					transport["headers"] = map[string]any{"Host": host}
				}
			}
		}
		return transport
	case "grpc":
		transport := map[string]any{
			"type": "grpc",
		}
		if settings != nil {
			if serviceName := singBoxString(settings["serviceName"]); serviceName != "" {
				transport["service_name"] = serviceName
			}
		}
		return transport
	case "h2":
		transport := map[string]any{
			"type": "http",
		}
		if settings != nil {
			if path := singBoxString(settings["path"]); path != "" {
				transport["path"] = path
			}
			if host := singBoxHostValue(settings["host"]); len(host) > 0 {
				transport["host"] = host
			}
		}
		return transport
	case "httpupgrade":
		transport := map[string]any{
			"type": "httpupgrade",
		}
		if settings != nil {
			if path := singBoxString(settings["path"]); path != "" {
				transport["path"] = path
			}
			if host := singBoxString(settings["host"]); host != "" {
				transport["host"] = host
			}
			if headers, ok := settings["headers"].(map[string]any); ok && len(headers) > 0 {
				transport["headers"] = headers
			}
		}
		return transport
	case "quic":
		return map[string]any{
			"type": "quic",
		}
	default:
		return nil
	}
}

func buildSingBoxUTLS(value any) map[string]any {
	settings, ok := value.(map[string]any)
	if !ok || !singBoxBool(settings["enabled"]) {
		return nil
	}

	utls := map[string]any{
		"enabled": true,
	}
	if fingerprint := singBoxString(settings["fingerprint"]); fingerprint != "" {
		utls["fingerprint"] = fingerprint
	}
	return utls
}

func buildSingBoxMultiplex(value any) map[string]any {
	settings, ok := value.(map[string]any)
	if !ok || !singBoxBool(settings["enabled"]) {
		return nil
	}

	multiplex := map[string]any{
		"enabled": true,
	}
	if protocol := singBoxString(settings["protocol"]); protocol != "" {
		multiplex["protocol"] = protocol
	}
	if maxConnections := singBoxInt(settings["max_connections"]); maxConnections > 0 {
		multiplex["max_connections"] = maxConnections
	}
	if minStreams := singBoxInt(settings["min_streams"]); minStreams > 0 {
		multiplex["min_streams"] = minStreams
	}
	if maxStreams := singBoxInt(settings["max_streams"]); maxStreams > 0 {
		multiplex["max_streams"] = maxStreams
	}
	if singBoxBool(settings["padding"]) {
		multiplex["padding"] = true
	}
	if brutal, ok := settings["brutal"].(map[string]any); ok && singBoxBool(brutal["enabled"]) {
		multiplex["brutal"] = map[string]any{
			"enabled":   true,
			"up_mbps":   singBoxInt(brutal["up_mbps"]),
			"down_mbps": singBoxInt(brutal["down_mbps"]),
		}
	}
	return multiplex
}

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

func singBoxString(value any) string {
	switch typed := value.(type) {
	case string:
		return typed
	case nil:
		return ""
	default:
		return toString(value)
	}
}

func singBoxInt(value any) int {
	switch typed := value.(type) {
	case int:
		return typed
	case int64:
		return int(typed)
	case float64:
		return int(typed)
	default:
		return 0
	}
}

func singBoxBool(value any) bool {
	switch typed := value.(type) {
	case bool:
		return typed
	case int:
		return typed != 0
	case int64:
		return typed != 0
	case float64:
		return typed != 0
	case string:
		return typed == "1" || typed == "true"
	default:
		return false
	}
}
