package protocol

import (
	"fmt"
	"strings"
	"xboard-go/internal/model"
	"xboard-go/internal/service"

	"github.com/goccy/go-yaml"
)

func GenerateClash(servers []model.Server, user model.User) (string, error) {
	return GenerateClashWithTemplate("clash", servers, user)
}

func GenerateClashWithTemplate(templateName string, servers []model.Server, user model.User) (string, error) {
	template := strings.TrimSpace(service.GetSubscribeTemplate(templateName))
	if template == "" {
		return generateClashFallback(servers, user), nil
	}

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

	proxies := make([]any, 0, len(servers))
	proxyNames := make([]string, 0, len(servers))
	for _, s := range servers {
		conf := service.BuildNodeConfig(&s)
		proxy := buildClashProxy(conf, user)
		if proxy == nil {
			continue
		}
		proxies = append(proxies, proxy)
		proxyNames = append(proxyNames, conf.Name)
	}

	config["proxies"] = append(anySlice(config["proxies"]), proxies...)
	config["proxy-groups"] = mergeClashProxyGroups(anySlice(config["proxy-groups"]), proxyNames)

	if _, ok := config["rules"]; !ok {
		config["rules"] = []any{"MATCH,Proxy"}
	}

	data, err := yaml.Marshal(config)
	if err != nil {
		return "", err
	}

	output := strings.ReplaceAll(string(data), "$app_name", service.MustGetString("app_name", "XBoard"))
	return output, nil
}

func buildClashProxy(conf service.NodeServerConfig, user model.User) map[string]any {
	switch conf.Protocol {
	case "shadowsocks":
		cipher, _ := conf.Cipher.(string)
		return map[string]any{
			"name":     conf.Name,
			"type":     "ss",
			"server":   conf.RawHost,
			"port":     conf.ServerPort,
			"cipher":   cipher,
			"password": user.UUID,
		}
	case "vmess":
		return map[string]any{
			"name":    conf.Name,
			"type":    "vmess",
			"server":  conf.RawHost,
			"port":    conf.ServerPort,
			"uuid":    user.UUID,
			"alterId": 0,
			"cipher":  "auto",
			"udp":     true,
		}
	case "trojan":
		return map[string]any{
			"name":     conf.Name,
			"type":     "trojan",
			"server":   conf.RawHost,
			"port":     conf.ServerPort,
			"password": user.UUID,
			"udp":      true,
		}
	default:
		return nil
	}
}

func mergeClashProxyGroups(groups []any, proxyNames []string) []any {
	if len(groups) == 0 {
		return []any{
			map[string]any{
				"name":    "Proxy",
				"type":    "select",
				"proxies": append([]any{"DIRECT"}, stringsToAny(proxyNames)...),
			},
		}
	}

	for index, item := range groups {
		group, ok := item.(map[string]any)
		if !ok {
			continue
		}

		group["proxies"] = appendUniqueAny(anySlice(group["proxies"]), stringsToAny(proxyNames)...)
		groups[index] = group
	}
	return groups
}

func anySlice(value any) []any {
	switch typed := value.(type) {
	case nil:
		return []any{}
	case []any:
		return append([]any{}, typed...)
	case []string:
		result := make([]any, 0, len(typed))
		for _, item := range typed {
			result = append(result, item)
		}
		return result
	default:
		return []any{}
	}
}

func stringsToAny(values []string) []any {
	result := make([]any, 0, len(values))
	for _, value := range values {
		result = append(result, value)
	}
	return result
}

func appendUniqueAny(base []any, values ...any) []any {
	existing := make(map[string]struct{}, len(base))
	for _, item := range base {
		existing[fmt.Sprint(item)] = struct{}{}
	}

	for _, item := range values {
		key := fmt.Sprint(item)
		if _, ok := existing[key]; ok {
			continue
		}
		base = append(base, item)
		existing[key] = struct{}{}
	}
	return base
}

func generateClashFallback(servers []model.Server, user model.User) string {
	var builder strings.Builder

	builder.WriteString("proxies:\n")
	var proxyNames []string
	for _, s := range servers {
		conf := service.BuildNodeConfig(&s)
		proxy := buildClashProxy(conf, user)
		if proxy == nil {
			continue
		}

		switch proxy["type"] {
		case "ss":
			builder.WriteString(fmt.Sprintf(
				"  - name: \"%s\"\n    type: ss\n    server: %s\n    port: %d\n    cipher: %s\n    password: %s\n",
				conf.Name,
				conf.RawHost,
				conf.ServerPort,
				fmt.Sprint(proxy["cipher"]),
				user.UUID,
			))
		case "vmess":
			builder.WriteString(fmt.Sprintf(
				"  - name: \"%s\"\n    type: vmess\n    server: %s\n    port: %d\n    uuid: %s\n    alterId: 0\n    cipher: auto\n    udp: true\n",
				conf.Name,
				conf.RawHost,
				conf.ServerPort,
				user.UUID,
			))
		case "trojan":
			builder.WriteString(fmt.Sprintf(
				"  - name: \"%s\"\n    type: trojan\n    server: %s\n    port: %d\n    password: %s\n    udp: true\n",
				conf.Name,
				conf.RawHost,
				conf.ServerPort,
				user.UUID,
			))
		}
		proxyNames = append(proxyNames, fmt.Sprintf("\"%s\"", conf.Name))
	}

	builder.WriteString("\nproxy-groups:\n")
	builder.WriteString("  - name: Proxy\n    type: select\n    proxies:\n      - DIRECT\n")
	for _, name := range proxyNames {
		builder.WriteString("      - " + name + "\n")
	}

	builder.WriteString("\nrules:\n")
	builder.WriteString("  - MATCH,Proxy\n")

	return builder.String()
}
