package tun

import (
	"fmt"
	"github.com/alibaba/kt-connect/pkg/kt/util"
	"github.com/rs/zerolog/log"
	"net"
	"os/exec"
	"strconv"
	"strings"
)

// CheckContext check everything needed for tun setup
func (s *Cli) CheckContext() error {
	if !util.CanRun(exec.Command("which", "ifconfig")) {
		return fmt.Errorf("failed to found 'ifconfig' command")
	}
	if !util.CanRun(exec.Command("which", "route")) {
		return fmt.Errorf("failed to found 'route' command")
	}
	if !util.CanRun(exec.Command("which", "netstat")) {
		return fmt.Errorf("failed to found 'netstat' command")
	}
	return nil
}

// SetRoute set specified ip range route to tun device
func (s *Cli) SetRoute(ipRange []string, excludeIpRange []string) error {
	var err, lastErr error
	anyRouteOk := false
	for i, r := range ipRange {
		log.Info().Msgf("Adding route to %s", r)
		tunIp := strings.Split(r, "/")[0]
		if i == 0 {
			// run command: ifconfig utun6 inet 172.20.0.0/16 172.20.0.0
			_, _, err = util.RunAndWait(exec.Command("ifconfig",
				s.GetName(),
				"inet",
				r,
				tunIp,
			))
		} else {
			// run command: ifconfig utun6 add 172.20.0.0/16 172.20.0.1
			_, _, err = util.RunAndWait(exec.Command("ifconfig",
				s.GetName(),
				"add",
				r,
				tunIp,
			))
		}
		if err != nil {
			log.Warn().Msgf("Failed to add ip addr %s to tun device", tunIp)
			lastErr = err
			continue
		} else {
			anyRouteOk = true
		}
		// run command: route add -net 172.20.0.0/16 -interface utun6
		_, _, err = util.RunAndWait(exec.Command("route",
			"add",
			"-net",
			r,
			"-interface",
			s.GetName(),
		))
		if err != nil {
			log.Warn().Msgf("Failed to set route %s to tun device", r)
			lastErr = err
		} else {
			anyRouteOk = true
		}
	}
	if !anyRouteOk {
		return AllRouteFailError{lastErr}
	}
	return lastErr
}

// CheckRoute check whether all route rule setup properly
func (s *Cli) CheckRoute(ipRange []string) []string {
	var failedIpRange []string
	// run command: netstat -rn
	out, _, err := util.RunAndWait(exec.Command("netstat",
		"-rn",
	))
	if err != nil {
		log.Warn().Msgf("Failed to get route table")
		return []string{}
	}
	_, _ = util.BackgroundLogger.Write([]byte(">> Get route: " + out + util.Eol))

	for _, ir := range ipRange {
		found := false
		for _, line := range strings.Split(out, util.Eol) {
			ip := strings.Split(ir, "/")[0]
			if strings.HasPrefix(line, ip) && strings.HasSuffix(strings.TrimSpace(line), s.GetName()) {
				found = true
				break
			}
		}
		if !found {
			failedIpRange = append(failedIpRange, ir)
		}
	}
	return failedIpRange
}

// RestoreRoute delete route rules made by kt
func (s *Cli) RestoreRoute() error {
	// Route will be auto removed when tun device destroyed
	return nil
}

var tunName = ""
func (s *Cli) GetName() string {
	if tunName != "" {
		return tunName
	}
	tunName = fmt.Sprintf("%s%d", util.TunNameMac, 9)
	if ifaces, err := net.Interfaces(); err == nil {
		tunN := 0
		for _, i := range ifaces {
			if strings.HasPrefix(i.Name, util.TunNameMac) {
				if num, err2 := strconv.Atoi(strings.TrimPrefix(i.Name, util.TunNameMac)); err2 == nil && num > tunN {
					tunN = num
				}
			}
		}
		tunName = fmt.Sprintf("%s%d", util.TunNameMac, tunN + 1)
	}
	return tunName
}
