package serverObj

import (
	"fmt"
	jsoniter "github.com/json-iterator/go"
	"github.com/v2rayA/v2rayA/conf"
	"github.com/v2rayA/v2rayA/kernel/coreObj"
	"github.com/v2rayA/v2rayA/pkg/util/log"
	"os/exec"
	"strconv"
	"strings"
)

var ErrUnsupportedPlugin = fmt.Errorf("unsupported plugin")

const PluginManagerScheme = "plugin-manager"

func init() {
	FromLinkRegister(PluginManagerScheme, NewPlugin)
	EmptyRegister(PluginManagerScheme, func() (ServerObj, error) {
		return new(Plugin), nil
	})
}

type Plugin struct {
	Name           string `json:"pm_name"`
	Host           string `json:"pm_add"`
	Port           string `json:"pm_port"`
	ProtocolToShow string `json:"pm_protocol"`
	Link           string `json:"pm_link"`

	Protocol string `json:"protocol"`
}

func NewPlugin(link string) (ServerObj, error) {
	if pm := conf.GetEnvironmentConfig().PluginManager; pm != "" {
		b, err := exec.Command(pm,
			"--stage=parse",
			fmt.Sprintf("--link=%v", link),
			fmt.Sprintf("--v2raya-confdir=%v", conf.GetEnvironmentConfig().Config),
		).CombinedOutput()
		if err != nil {
			log.Info("[PluginManager] failed to parse %v: %v", link, string(b))
			return nil, err
		}
		nExpected := 4
		fields := strings.SplitN(strings.TrimSpace(string(b)), ";", nExpected)
		if len(fields) < nExpected {
			return nil, fmt.Errorf("[PluginManager] insufficient return value: %v. Expected number: %v", fields, nExpected)
		}
		return &Plugin{
			Name:           fields[0],
			Host:           fields[1],
			Port:           fields[2],
			ProtocolToShow: fields[3],
			Link:           link,
			Protocol:       PluginManagerScheme,
		}, nil
	}
	return nil, ErrUnsupportedPlugin
}

func (v *Plugin) Configuration(info PriorInfo) (c Configuration, err error) {
	if pm := conf.GetEnvironmentConfig().PluginManager; pm != "" {
		b, err := exec.Command(pm,
			"--stage=configuration",
			fmt.Sprintf("--link=%v", v.Link),
			fmt.Sprintf("--port=%v", info.PluginPort),
			fmt.Sprintf("--v2raya-confdir=%v", conf.GetEnvironmentConfig().Config),
		).CombinedOutput()
		sOut := strings.TrimSpace(string(b))
		if err != nil {
			return Configuration{}, fmt.Errorf("[PluginManager] Error when Configuration: %w: %v", err, sOut)
		}
		var core coreObj.OutboundObject
		if err := jsoniter.UnmarshalFromString(sOut, &core); err != nil {
			return Configuration{}, fmt.Errorf("[PluginManager] Error when Configuration: failed to parse json output")
		}
		core.Tag = info.Tag
		return Configuration{
			CoreOutbound:            core,
			PluginChain:             "",
			UDPSupport:              true,
			PluginManagerServerLink: v.Link,
		}, nil
	} else {
		return Configuration{}, ErrUnsupportedPlugin
	}
}

func (v *Plugin) ExportToURL() string {
	if v.Link == "" {
		log.Warn("You may need to re-import the node for PluginManager to take effect")
	}
	return v.Link
}

func (v *Plugin) NeedPluginPort() bool {
	return true
}

func (v *Plugin) ProtoToShow() string {
	return v.ProtocolToShow + "(plugin)"
}

func (v *Plugin) GetProtocol() string {
	return v.ProtocolToShow
}

func (v *Plugin) GetHostname() string {
	return v.Host
}

func (v *Plugin) GetPort() int {
	p, _ := strconv.Atoi(v.Port)
	return p
}

func (v *Plugin) GetName() string {
	return v.Name
}

func (v *Plugin) SetName(name string) {
	v.Name = name
}
