package config

import (
	"encoding/base64"
	"fmt"
	"log/slog"
	"net/netip"
	"os"
	"strings"

	"github.com/bepass-org/warp-plus/warp"
	"github.com/hiddify/hiddify-core/v2/common"
	C "github.com/sagernet/sing-box/constant"

	// "github.com/bepass-org/wireguard-go/warp"
	"github.com/hiddify/hiddify-core/v2/db"

	"github.com/sagernet/sing-box/option"
	T "github.com/sagernet/sing-box/option"
)

type SingboxConfig struct {
	Type          string   `json:"type"`
	Tag           string   `json:"tag"`
	Server        string   `json:"server"`
	ServerPort    int      `json:"server_port"`
	LocalAddress  []string `json:"local_address"`
	PrivateKey    string   `json:"private_key"`
	PeerPublicKey string   `json:"peer_public_key"`
	Reserved      []int    `json:"reserved"`
	MTU           int      `json:"mtu"`
}

func wireGuardToSingbox(wgConfig WarpWireguardConfig, server string, port uint16) (*T.Outbound, error) {
	clientID, _ := base64.StdEncoding.DecodeString(wgConfig.ClientID)
	if len(clientID) < 2 {
		clientID = []byte{0, 0, 0}
	}
	out := T.Outbound{
		Type: "wireguard",
		Tag:  "WARP",
		WireGuardOptions: T.WireGuardOutboundOptions{
			ServerOptions: T.ServerOptions{
				Server:     server,
				ServerPort: port,
			},

			PrivateKey:    wgConfig.PrivateKey,
			PeerPublicKey: wgConfig.PeerPublicKey,
			Reserved:      []uint8{clientID[0], clientID[1], clientID[2]},
			// Reserved: []uint8{0, 0, 0},
			MTU: 1330,
		},
	}
	ips := []string{wgConfig.LocalAddressIPv4 + "/24", wgConfig.LocalAddressIPv6 + "/128"}

	for _, addr := range ips {
		if addr == "" {
			continue
		}
		prefix, err := netip.ParsePrefix(addr)
		if err != nil {
			return nil, err // Handle the error appropriately
		}
		out.WireGuardOptions.LocalAddress = append(out.WireGuardOptions.LocalAddress, prefix)
	}
	return &out, nil
}

func getRandomIP() string {
	ipPort, err := warp.RandomWarpEndpoint(true, true)
	if err == nil {
		return ipPort.Addr().String()
	}
	return "engage.cloudflareclient.com"
}

func generateWarp(license string, host string, port uint16, fakePackets string, fakePacketsSize string, fakePacketsDelay string, fakePacketsMode string) (*T.Outbound, error) {
	_, _, wgConfig, err := GenerateWarpInfo(license, "", "")
	if err != nil {
		return nil, err
	}
	if wgConfig == nil {
		return nil, fmt.Errorf("invalid warp config")
	}

	return GenerateWarpSingbox(*wgConfig, host, port, fakePackets, fakePacketsSize, fakePacketsDelay, fakePacketsMode)
}

func GenerateWarpSingbox(wgConfig WarpWireguardConfig, host string, port uint16, fakePackets string, fakePacketsSize string, fakePacketsDelay string, fakePacketMode string) (*T.Outbound, error) {
	if host == "" {
		host = "auto4"
	}

	if (host == "auto" || host == "auto4" || host == "auto6") && fakePackets == "" {
		fakePackets = "1-3"
	}
	if fakePackets != "" && fakePacketsSize == "" {
		fakePacketsSize = "10-30"
	}
	if fakePackets != "" && fakePacketsDelay == "" {
		fakePacketsDelay = "10-30"
	}
	singboxConfig, err := wireGuardToSingbox(wgConfig, host, port)
	if err != nil {
		fmt.Printf("%v %v", singboxConfig, err)
		return nil, err
	}

	singboxConfig.WireGuardOptions.FakePackets = fakePackets
	singboxConfig.WireGuardOptions.FakePacketsSize = fakePacketsSize
	singboxConfig.WireGuardOptions.FakePacketsDelay = fakePacketsDelay
	singboxConfig.WireGuardOptions.FakePacketsMode = fakePacketMode

	return singboxConfig, nil
}

func GenerateWarpInfo(license string, oldAccountId string, oldAccessToken string) (*warp.Identity, string, *WarpWireguardConfig, error) {
	if oldAccountId != "" && oldAccessToken != "" {
		err := warp.DeleteDevice(oldAccessToken, oldAccountId)
		if err != nil {
			fmt.Printf("Error in removing old device: %v\n", err)
		} else {
			fmt.Printf("Old Device Removed")
		}
	}
	l := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
	identity, err := warp.CreateIdentityOnly(l, license)
	res := "Error!"
	var warpcfg WarpWireguardConfig
	if err == nil {
		res = "Success"
		res = fmt.Sprintf("Warp+ enabled: %t\n", identity.Account.WarpPlus)
		res += fmt.Sprintf("\nAccount type: %s\n", identity.Account.AccountType)
		warpcfg = WarpWireguardConfig{
			PrivateKey:       identity.PrivateKey,
			PeerPublicKey:    identity.Config.Peers[0].PublicKey,
			LocalAddressIPv4: identity.Config.Interface.Addresses.V4,
			LocalAddressIPv6: identity.Config.Interface.Addresses.V6,
			ClientID:         identity.Config.ClientID,
		}
	}

	return &identity, res, &warpcfg, err
}

func getOrGenerateWarpLocallyIfNeeded(warpOptions *WarpOptions) WarpWireguardConfig {
	if warpOptions.WireguardConfig.PrivateKey != "" {
		return warpOptions.WireguardConfig
	}
	table := db.GetTable[WarpOptions]()
	dbWarpOptions, err := table.Get(warpOptions.Id)
	if err == nil && dbWarpOptions.WireguardConfig.PrivateKey != "" {
		return warpOptions.WireguardConfig
	}
	license := ""
	if len(warpOptions.Id) == 26 { // warp key is 26 characters long
		license = warpOptions.Id
	} else if len(warpOptions.Id) > 28 && warpOptions.Id[2] == '_' { // warp key is 26 characters long
		license = warpOptions.Id[3:]
	}

	accountidentity, _, wireguardConfig, err := GenerateWarpInfo(license, warpOptions.Account.AccountID, warpOptions.Account.AccessToken)
	if err != nil {
		return WarpWireguardConfig{}
	}
	warpOptions.Account = WarpAccount{
		AccountID:   accountidentity.ID,
		AccessToken: accountidentity.Token,
	}
	warpOptions.WireguardConfig = *wireguardConfig
	table.UpdateInsert(warpOptions)

	return *wireguardConfig
}

func patchWarp(base *option.Outbound, configOpt *HiddifyOptions, final bool, staticIpsDns map[string][]string) error {
	if base.Type == C.TypeCustom {
		if warp, ok := base.CustomOptions["warp"].(map[string]interface{}); ok {
			key, _ := warp["key"].(string)
			host, _ := warp["host"].(string)
			port, _ := warp["port"].(uint16)
			detour, _ := warp["detour"].(string)
			fakePackets, _ := warp["fake_packets"].(string)
			fakePacketsSize, _ := warp["fake_packets_size"].(string)
			fakePacketsDelay, _ := warp["fake_packets_delay"].(string)
			fakePacketsMode, _ := warp["fake_packets_mode"].(string)
			var warpOutbound *T.Outbound
			var err error

			is_saved_key := len(key) > 1 && key[0] == 'p'

			if (configOpt == nil || !final) && is_saved_key {
				return nil
			}
			var wireguardConfig WarpWireguardConfig
			if is_saved_key {
				var warpOpt *WarpOptions
				if key == "p1" {
					warpOpt = &configOpt.Warp
				} else if key == "p2" {
					warpOpt = &configOpt.Warp2
				} else {
					warpOpt = &WarpOptions{
						Id: key,
					}
				}
				warpOpt.Id = key

				wireguardConfig = getOrGenerateWarpLocallyIfNeeded(warpOpt)
			} else {
				_, _, wgConfig, err := GenerateWarpInfo(key, "", "")
				if err != nil {
					return err
				}
				wireguardConfig = *wgConfig
			}
			warpOutbound, err = GenerateWarpSingbox(wireguardConfig, host, port, fakePackets, fakePacketsSize, fakePacketsDelay, fakePacketsMode)
			if err != nil {
				fmt.Printf("Error generating warp config: %v", err)
				return err
			}
			warpOutbound.WireGuardOptions.Detour = detour
			base.Type = C.TypeWireGuard
			base.WireGuardOptions = warpOutbound.WireGuardOptions
		}
	}

	if final && base.Type == C.TypeWireGuard {
		host := base.WireGuardOptions.Server

		if host == "default" || host == "random" || host == "auto" || host == "auto4" || host == "auto6" || isBlockedDomain(host) {
			// if base.WireGuardOptions.Detour != "" {
			// 	base.WireGuardOptions.Server = "162.159.192.1"
			// } else {
			rndDomain := strings.ToLower(generateRandomString(20))
			staticIpsDns[rndDomain] = []string{}
			if host != "auto4" {
				if host == "auto6" || common.CanConnectIPv6() {
					randomIpPort, _ := warp.RandomWarpEndpoint(false, true)
					staticIpsDns[rndDomain] = append(staticIpsDns[rndDomain], randomIpPort.Addr().String())
				}
			}
			if host != "auto6" {
				randomIpPort, _ := warp.RandomWarpEndpoint(true, false)
				staticIpsDns[rndDomain] = append(staticIpsDns[rndDomain], randomIpPort.Addr().String())
			}
			base.WireGuardOptions.Server = rndDomain
			// }
		}
		if base.WireGuardOptions.ServerPort == 0 {
			port := warp.RandomWarpPort()
			base.WireGuardOptions.ServerPort = port
		}

		if base.WireGuardOptions.Detour != "" {
			if base.WireGuardOptions.MTU < 100 {
				base.WireGuardOptions.MTU = 1280
			}
			base.WireGuardOptions.FakePackets = ""
			base.WireGuardOptions.FakePacketsDelay = ""
			base.WireGuardOptions.FakePacketsSize = ""
		}
		// if base.WireGuardOptions.Detour == "" {
		// 	base.WireGuardOptions.GSO = runtime.GOOS != "windows"
		// }
	}

	return nil
}
