package main

import "C"
import (
	"github.com/metacubex/mihomo/adapter/inbound"
	"github.com/metacubex/mihomo/component/dialer"
	"github.com/metacubex/mihomo/component/process"
	"github.com/metacubex/mihomo/component/resolver"
	"github.com/metacubex/mihomo/config"
	"github.com/metacubex/mihomo/dns"
	"github.com/metacubex/mihomo/hub/executor"
	"github.com/metacubex/mihomo/listener"
	"github.com/metacubex/mihomo/log"
	"github.com/metacubex/mihomo/tunnel"
	"os"
	"os/exec"
	"runtime"
	"syscall"
)

type GenerateConfigParams struct {
	ProfilePath *string           `json:"profile-path"`
	Config      *config.RawConfig `json:"config" `
	IsPatch     *bool             `json:"is-patch"`
}

type ChangeProxyParams struct {
	GroupName *string `json:"group-name"`
	ProxyName *string `json:"proxy-name"`
}

type TestDelayParams struct {
	ProxyName string `json:"proxy-name"`
	Timeout   int64  `json:"timeout"`
}

type Delay struct {
	Name  string `json:"name"`
	Value int32  `json:"value"`
}

type Process struct {
	Uid     uint32 `json:"uid"`
	Network string `json:"network"`
	Source  string `json:"source"`
	Target  string `json:"target"`
}

func restartExecutable(execPath string) {
	var err error
	executor.Shutdown()
	if runtime.GOOS == "windows" {
		cmd := exec.Command(execPath, os.Args[1:]...)
		log.Infoln("restarting: %q %q", execPath, os.Args[1:])
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err = cmd.Start()
		if err != nil {
			log.Fatalln("restarting: %s", err)
		}

		os.Exit(0)
	}

	log.Infoln("restarting: %q %q", execPath, os.Args[1:])
	err = syscall.Exec(execPath, os.Args, os.Environ())
	if err != nil {
		log.Fatalln("restarting: %s", err)
	}
}

func readFile(path string) ([]byte, error) {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return nil, err
	}
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}

	return data, err
}

func getRawConfigWithPath(path *string) *config.RawConfig {
	if path == nil {
		return config.DefaultRawConfig()
	} else {
		bytes, err := readFile(*path)
		if err != nil {
			log.Errorln("getProfile readFile error %v", err)
			return config.DefaultRawConfig()
		}
		prof, err := config.UnmarshalRawConfig(bytes)
		if err != nil {
			log.Errorln("getProfile UnmarshalRawConfig error %v", err)
			return config.DefaultRawConfig()
		}
		return prof
	}
}

func decorationConfig(profilePath *string, cfg config.RawConfig) *config.RawConfig {
	prof := getRawConfigWithPath(profilePath)
	overwriteConfig(prof, cfg)
	return prof
}

func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) {
	targetConfig.ExternalController = ""
	targetConfig.ExternalUI = ""
	targetConfig.Interface = ""
	targetConfig.ExternalUIURL = ""
	targetConfig.IPv6 = patchConfig.IPv6
	targetConfig.LogLevel = patchConfig.LogLevel
	targetConfig.FindProcessMode = process.FindProcessAlways
	targetConfig.AllowLan = patchConfig.AllowLan
	targetConfig.MixedPort = patchConfig.MixedPort
	targetConfig.Mode = patchConfig.Mode
	targetConfig.Tun.Enable = patchConfig.Tun.Enable
	targetConfig.Tun.Device = patchConfig.Tun.Device
	targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
	targetConfig.Tun.Stack = patchConfig.Tun.Stack
	if targetConfig.DNS.Enable == false {
		targetConfig.DNS = patchConfig.DNS
	} else {
		targetConfig.DNS.UseHosts = patchConfig.DNS.UseHosts
		targetConfig.DNS.EnhancedMode = patchConfig.DNS.EnhancedMode
		targetConfig.DNS.IPv6 = patchConfig.DNS.IPv6
		targetConfig.DNS.DefaultNameserver = append(patchConfig.DNS.DefaultNameserver, targetConfig.DNS.DefaultNameserver...)
		targetConfig.DNS.NameServer = append(patchConfig.DNS.NameServer, targetConfig.DNS.NameServer...)
		targetConfig.DNS.FakeIPFilter = append(patchConfig.DNS.FakeIPFilter, targetConfig.DNS.FakeIPFilter...)
		targetConfig.DNS.Fallback = append(patchConfig.DNS.Fallback, targetConfig.DNS.Fallback...)
		if runtime.GOOS == "android" {
			targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder)
		} else if runtime.GOOS == "windows" {
			targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder)
		}
	}
}

func patchConfig(general *config.General) {
	log.Infoln("[Apply] patch")
	listener.SetAllowLan(general.AllowLan)
	inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
	inbound.SetAllowedIPs(general.LanAllowedIPs)
	inbound.SetDisAllowedIPs(general.LanDisAllowedIPs)
	listener.SetBindAddress(general.BindAddress)
	tunnel.SetSniffing(general.Sniffing)
	dialer.SetTcpConcurrent(general.TCPConcurrent)
	dialer.DefaultInterface.Store(general.Interface)
	listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
	listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
	listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
	listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
	listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
	listener.ReCreateTun(general.Tun, tunnel.Tunnel)
	listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
	listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
	listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel)
	listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
	tunnel.SetMode(general.Mode)
	log.SetLevel(general.LogLevel)
	resolver.DisableIPv6 = !general.IPv6
}

func applyConfig(isPatch bool) bool {
	if currentConfig == nil {
		return false
	}
	cfg, err := config.ParseRawConfig(currentConfig)
	if err != nil {
		cfg, _ = config.ParseRawConfig(config.DefaultRawConfig())
	}
	if isPatch {
		patchConfig(cfg.General)
	} else {
		executor.ApplyConfig(cfg, true)

	}
	return true
}
