// util.go v1.0
package sandbox

import (
	"fmt"
	"io"
	"net"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/ssgo/log"
	"github.com/ssgo/u"
)

var currentIds = map[string]bool{}
var idLock sync.Mutex

func getId() string {
	id := u.Id8()
	idLock.Lock()
	defer idLock.Unlock()
	for currentIds[id] {
		id = u.Id8()
	}
	currentIds[id] = true
	return id
}

func releaseId(id string) {
	idLock.Lock()
	defer idLock.Unlock()
	delete(currentIds, id)
}

func (s *Sandbox) assertLog(title string, err error, extra ...any) {
	if s.config.NoLog {
		return
	}
	if err != nil {
		extra = append(extra, "err", err.Error())
		log.DefaultLogger.Info("[Sandbox] "+title+" failed", extra...)
	} else {
		log.DefaultLogger.Info("[Sandbox] "+title+" success", extra...)
	}
}

func (s *Sandbox) log(title string, extra ...any) {
	if s.config.NoLog {
		return
	}
	log.DefaultLogger.Info("[Sandbox] "+title, extra...)
}

type Volumes struct {
	volumes       []Volume
	indexByTarget map[string]int
}

func (v *Volumes) Add(vv ...Volume) {
	for _, v1 := range vv {
		if v1.Target == "" {
			v1.Target = v1.Source
		}
		if v1.Target == "/proc" || v1.Target == "/sys" {
			continue
		}
		if _, ok := v.indexByTarget[v1.Target]; ok {
			v.volumes[v.indexByTarget[v1.Target]] = v1
		} else {
			v.volumes = append(v.volumes, v1)
			v.indexByTarget[v1.Target] = len(v.volumes) - 1
		}
	}
}

func (v *Volumes) Get() []Volume {
	return v.volumes
}

func NewVolumes() *Volumes {
	return &Volumes{
		volumes:       []Volume{},
		indexByTarget: map[string]int{},
	}
}

func copyLog(workDir string, startTime int64) (int, error) {
	logDate := time.Unix(startTime, 0).Format("20060102")
	lastLogFile := filepath.Join(workDir, "stdout.log")
	errorLogFile := filepath.Join(workDir, "stderr.log")
	lastLogDest := filepath.Join(workDir, "logs", "stdout_"+logDate+".log")
	errorLogDest := filepath.Join(workDir, "logs", "stderr_"+logDate+".log")
	ok := 0
	var err1 error
	if !u.FileExists(lastLogDest) {
		if err := u.CopyFile(lastLogFile, lastLogDest); err == nil {
			ok++
		} else {
			err1 = err
		}
	} else {
		if to, err := os.OpenFile(lastLogDest, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err == nil {
			defer to.Close()
			if from, err := os.Open(lastLogFile); err == nil {
				defer from.Close()
				to.WriteString("\n")
				if _, err := io.Copy(to, from); err == nil {
					ok++
				} else {
					err1 = err
				}
			}
		}
	}
	if !u.FileExists(errorLogDest) {
		if err := u.CopyFile(errorLogFile, errorLogDest); err == nil {
			ok++
		} else {
			err1 = err
		}
	} else {
		if to, err := os.OpenFile(errorLogDest, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err == nil {
			defer to.Close()
			if from, err := os.Open(errorLogFile); err == nil {
				defer from.Close()
				to.WriteString("\n")
				if _, err := io.Copy(to, from); err == nil {
					ok++
				} else {
					err1 = err
				}
			}
		}
	}
	return ok, err1
}

type NetRule struct {
	IP   [16]byte // 统一 IPv6 长度
	Mask int8     // -1 代表不校验掩码 (单IP), 0-128 代表掩码位
	Port uint16   // 0 代表所有端口
	IsV6 uint8    // 0: v4, 1: v6
}

func ParseNetRule(s string) (*NetRule, error) {
	rule := &NetRule{Mask: -1}
	hostStr := s

	// 1. 处理端口 (从后往前找最后一个冒号，且排除 IPv6 的冒号)
	if lastColon := strings.LastIndex(s, ":"); lastColon != -1 && !strings.HasSuffix(s, "]") {
		// 检查冒号后面是否是纯数字（端口），如果不是，说明是 IPv6 地址的一部分
		if port, err := strconv.Atoi(s[lastColon+1:]); err == nil {
			rule.Port = uint16(port)
			hostStr = s[:lastColon]
		}
	}

	// 2. 处理 CIDR
	ipStr := hostStr
	if strings.Contains(hostStr, "/") {
		_, ipNet, err := net.ParseCIDR(hostStr)
		if err != nil {
			return nil, err
		}
		ones, _ := ipNet.Mask.Size()
		rule.Mask = int8(ones)
		ipStr = ipNet.IP.String()
	}

	// 3. 处理 IP (去掉 IPv6 的方括号)
	ipStr = strings.Trim(ipStr, "[]")
	ip := net.ParseIP(ipStr)
	if ip == nil {
		return nil, fmt.Errorf("invalid ip: %s", ipStr)
	}

	if ip4 := ip.To4(); ip4 != nil {
		copy(rule.IP[:4], ip4)
		rule.IsV6 = 0
	} else {
		copy(rule.IP[:], ip.To16())
		rule.IsV6 = 1
	}

	return rule, nil
}
