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)
		outbound := buildSingBoxOutbound(conf, user)
		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, user model.User) map[string]any {
	outbound := map[string]any{
		"tag":         conf.Name,
		"server":      conf.RawHost,
		"server_port": conf.ServerPort,
	}

	switch conf.Protocol {
	case "shadowsocks":
		outbound["type"] = "shadowsocks"
		outbound["method"] = conf.Cipher
		outbound["password"] = user.UUID
	case "vmess":
		outbound["type"] = "vmess"
		outbound["uuid"] = user.UUID
		outbound["security"] = "auto"
	case "trojan":
		outbound["type"] = "trojan"
		outbound["password"] = user.UUID
	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)
		outbound := map[string]interface{}{
			"tag":         conf.Name,
			"server":      conf.RawHost,
			"server_port": conf.ServerPort,
		}

		switch conf.Protocol {
		case "shadowsocks":
			outbound["type"] = "shadowsocks"
			outbound["method"] = conf.Cipher
			outbound["password"] = user.UUID
		case "vmess":
			outbound["type"] = "vmess"
			outbound["uuid"] = user.UUID
			outbound["security"] = "auto"
		case "trojan":
			outbound["type"] = "trojan"
			outbound["password"] = user.UUID
		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
}
