package main

import "C"
import (
	bridge "core/dart-bridge"
	"encoding/json"
	"fmt"
	"github.com/metacubex/mihomo/adapter"
	"github.com/metacubex/mihomo/adapter/outboundgroup"
	"github.com/metacubex/mihomo/common/utils"
	"github.com/metacubex/mihomo/config"
	"github.com/metacubex/mihomo/constant"
	"github.com/metacubex/mihomo/hub/executor"
	"github.com/metacubex/mihomo/log"
	"github.com/metacubex/mihomo/tunnel"
	"github.com/metacubex/mihomo/tunnel/statistic"
	"golang.org/x/net/context"
	"os"
	"runtime"
	"time"
	"unsafe"
)

var currentConfig *config.RawConfig

var isInit = false

//export initClash
func initClash(homeDirStr *C.char) bool {
	if !isInit {
		constant.SetHomeDir(C.GoString(homeDirStr))
		isInit = true
	}
	return isInit
}

//export getIsInit
func getIsInit() bool {
	return isInit
}

//export restartClash
func restartClash() bool {
	execPath, _ := os.Executable()
	go restartExecutable(execPath)
	return true
}

//export shutdownClash
func shutdownClash() bool {
	executor.Shutdown()
	runtime.GC()
	isInit = false
	currentConfig = nil
	return true
}

//export validateConfig
func validateConfig(s *C.char) bool {
	bytes := []byte(C.GoString(s))
	_, err := config.UnmarshalRawConfig(bytes)
	return err == nil
}

//export updateConfig
func updateConfig(s *C.char) bool {
	paramsString := C.GoString(s)
	var params = &GenerateConfigParams{}
	err := json.Unmarshal([]byte(paramsString), params)
	if err != nil {
		log.Errorln("generateConfig Unmarshal error %v", err)
		return false
	}
	prof := decorationConfig(params.ProfilePath, *params.Config)
	currentConfig = prof
	if *params.IsPatch {
		return applyConfig(true)
	} else {
		return applyConfig(false)
	}
}

//export getProxies
func getProxies() *C.char {
	data, err := json.Marshal(tunnel.Proxies())
	if err != nil {
		return C.CString("")
	}
	return C.CString(string(data))
}

//export changeProxy
func changeProxy(s *C.char) bool {
	paramsString := C.GoString(s)
	var params = &ChangeProxyParams{}
	err := json.Unmarshal([]byte(paramsString), params)
	if err != nil {
		log.Infoln("Unmarshal ChangeProxyParams %v", err)
		return false
	}
	proxies := tunnel.Proxies()
	proxy := proxies[*params.GroupName]
	if proxy == nil {
		return false
	}
	log.Infoln("change proxy %s", proxy.Name())
	adapterProxy := proxy.(*adapter.Proxy)
	selector, ok := adapterProxy.ProxyAdapter.(*outboundgroup.Selector)
	if !ok {
		return false
	}
	if err := selector.Set(*params.ProxyName); err != nil {
		return false
	}
	return true
}

//export getTraffic
func getTraffic() *C.char {
	up, down := statistic.DefaultManager.Now()
	traffic := map[string]int64{
		"up":   up,
		"down": down,
	}
	data, err := json.Marshal(traffic)
	if err != nil {
		fmt.Println("Error:", err)
		return C.CString("")
	}
	return C.CString(string(data))
}

//export asyncTestDelay
func asyncTestDelay(s *C.char) {
	go func() {
		paramsString := C.GoString(s)
		var params = &TestDelayParams{}
		err := json.Unmarshal([]byte(paramsString), params)
		if err != nil {
			return
		}

		expectedStatus, err := utils.NewUnsignedRanges[uint16]("")
		if err != nil {
			return
		}

		ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout))
		defer cancel()

		proxies := tunnel.Proxies()
		proxy := proxies[params.ProxyName]

		delayData := &Delay{
			Name: params.ProxyName,
		}

		message := bridge.Message{
			Type: bridge.Delay,
			Data: delayData,
		}

		if proxy == nil {
			delayData.Value = -1
			bridge.SendMessage(message)
			return
		}

		delay, err := proxy.URLTest(ctx, constant.DefaultTestURL, expectedStatus)
		if err != nil || delay == 0 {
			delayData.Value = -1
			bridge.SendMessage(message)
			return
		}

		delayData.Value = int32(delay)
		bridge.SendMessage(message)
	}()
}

//export getVersionInfo
func getVersionInfo() *C.char {
	versionInfo := map[string]string{
		"clashName": constant.Name,
		"version":   constant.Version,
	}
	data, err := json.Marshal(versionInfo)
	if err != nil {
		fmt.Println("Error:", err)
		return C.CString("")
	}
	return C.CString(string(data))
}

//export getConnections
func getConnections() *C.char {
	snapshot := statistic.DefaultManager.Snapshot()
	data, err := json.Marshal(snapshot)
	if err != nil {
		fmt.Println("Error:", err)
		return C.CString("")
	}
	return C.CString(string(data))
}

//export closeConnections
func closeConnections() bool {
	statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
		err := c.Close()
		if err != nil {
			return false
		}
		return true
	})
	return true
}

//export closeConnection
func closeConnection(id *C.char) bool {
	connectionId := C.GoString(id)

	err := statistic.DefaultManager.Get(connectionId).Close()
	if err != nil {
		return false
	}
	return true
}

//export getProviders
func getProviders() *C.char {
	data, err := json.Marshal(tunnel.Providers())
	if err != nil {
		return C.CString("")
	}
	return C.CString(string(data))
}

//export getProvider
func getProvider(name *C.char) *C.char {
	providerName := C.GoString(name)
	providers := tunnel.Providers()
	var provider = providers[providerName]
	data, err := json.Marshal(provider)
	if err != nil {
		return C.CString("")
	}
	return C.CString(string(data))
}

//export initNativeApiBridge
func initNativeApiBridge(api unsafe.Pointer, port C.longlong) {
	bridge.InitDartApi(api)
	i := int64(port)
	bridge.Port = &i
}
