package tidbruntime

import (
	"errors"
	"fmt"
	"net"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"syscall"
)

type skipTestError struct {
	reason string
}

func (err *skipTestError) Error() string {
	if err == nil {
		return ""
	}
	return err.reason
}

// SkipReason returns the reason for a skippable integration-test setup error.
func SkipReason(err error) (string, bool) {
	var skipErr *skipTestError
	if errors.As(err, &skipErr) {
		return skipErr.reason, true
	}
	return "", false
}

func ensureCommandAvailable(name string) error {
	if _, err := exec.LookPath(name); err == nil {
		return nil
	}
	return &skipTestError{reason: fmt.Sprintf("%s is required for integration tests", name)}
}

func freeTCPPort() (int, error) {
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return 0, fmt.Errorf("find free tcp port: %w", err)
	}
	defer listener.Close()
	return listener.Addr().(*net.TCPAddr).Port, nil
}

func processExists(pid int) bool {
	if pid <= 0 {
		return false
	}

	process, err := os.FindProcess(pid)
	if err != nil {
		return false
	}
	err = process.Signal(syscall.Signal(0))
	if err == nil {
		return !processIsZombie(pid)
	}
	if errors.Is(err, os.ErrProcessDone) {
		return false
	}

	var errno syscall.Errno
	if errors.As(err, &errno) && errno == syscall.ESRCH {
		return false
	}
	return true
}

func processIsZombie(pid int) bool {
	if pid <= 0 {
		return false
	}
	output, err := exec.Command("ps", "-o", "stat=", "-p", strconv.Itoa(pid)).Output()
	if err != nil {
		return false
	}
	return strings.HasPrefix(strings.TrimSpace(string(output)), "Z")
}
