// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

package main

import (
	"flag"
	"io"
	"os"
	"slices"
	"strings"
	"testing"
)

// registerTestFlags registers all flags from the testing package with the
// provided flag set. It does so by calling testing.Init() and then iterating
// over all flags registered on flag.CommandLine.
func registerTestFlags(fs *flag.FlagSet) {
	testing.Init()
	type bv interface {
		IsBoolFlag() bool
	}

	flag.CommandLine.VisitAll(func(f *flag.Flag) {
		if b, ok := f.Value.(bv); ok && b.IsBoolFlag() {
			fs.Bool(f.Name, f.DefValue == "true", f.Usage)
			if name, ok := strings.CutPrefix(f.Name, "test."); ok {
				fs.Bool(name, f.DefValue == "true", f.Usage)
			}
			return
		}

		// We don't actually care about the value of the flag, so we just
		// register it as a string. The values will be passed to `go test` which
		// will parse and validate them anyway.
		fs.String(f.Name, f.DefValue, f.Usage)
		if name, ok := strings.CutPrefix(f.Name, "test."); ok {
			fs.String(name, f.DefValue, f.Usage)
		}
	})
}

// splitArgs splits args into three parts as consumed by go test.
//
//	go test [build/test flags] [packages] [build/test flags & test binary flags]
//
// We return these as three slices of strings [pre] [pkgs] [post].
//
// It is used to split the arguments passed to testwrapper into the arguments
// passed to go test and the arguments passed to the tests.
func splitArgs(args []string) (pre, pkgs, post []string, _ error) {
	if len(args) == 0 {
		return nil, nil, nil, nil
	}

	fs := newTestFlagSet()
	// Parse stops at the first non-flag argument, so this allows us
	// to parse those as values and then reconstruct them as args.
	if err := fs.Parse(args); err != nil {
		return nil, nil, nil, err
	}
	fs.Visit(func(f *flag.Flag) {
		if f.Value.String() != f.DefValue && f.DefValue != "false" {
			pre = append(pre, "-"+f.Name, f.Value.String())
		} else {
			pre = append(pre, "-"+f.Name)
		}
	})

	// fs.Args() now contains [packages]+[build/test flags & test binary flags],
	// to split it we need to find the first non-flag argument.
	rem := fs.Args()
	ix := slices.IndexFunc(rem, func(s string) bool { return strings.HasPrefix(s, "-") })
	if ix == -1 {
		return pre, rem, nil, nil
	}
	pkgs = rem[:ix]
	post = rem[ix:]
	return pre, pkgs, post, nil
}

func newTestFlagSet() *flag.FlagSet {
	fs := flag.NewFlagSet("testwrapper", flag.ContinueOnError)
	fs.SetOutput(io.Discard)

	// Register all flags from the testing package.
	registerTestFlags(fs)
	// Also register the -exec flag, which is not part of the testing package.
	// TODO(maisem): figure out what other flags we need to register explicitly.
	fs.String("exec", "", "Command to run tests with")
	fs.Bool("race", false, "build with race detector")
	return fs
}

// testingVerbose reports whether the test is being run with verbose logging.
var testingVerbose = func() bool {
	verbose := false

	// Likely doesn't matter, but to be correct follow the go flag parsing logic
	// of overriding previous values.
	for _, arg := range os.Args[1:] {
		switch arg {
		case "-test.v", "--test.v",
			"-test.v=true", "--test.v=true",
			"-v", "--v",
			"-v=true", "--v=true":
			verbose = true
		case "-test.v=false", "--test.v=false",
			"-v=false", "--v=false":
			verbose = false
		}
	}
	return verbose
}()