package opt_test

import (
	"fmt"
	"strconv"
	"testing"

	"git.sr.ht/~rjarry/go-opt/v2"
	"github.com/stretchr/testify/assert"
)

type OptionStruct struct {
	Jobs    int     `opt:"-j,--jobs" required:"true"`
	Delay   float64 `opt:"--delay" default:"0.5"`
	Zero    bool    `opt:"-z" aliases:"baz"`
	Backoff bool    `opt:"-B,--backoff"`
	Name    string  `opt:"name" aliases:"bar" action:"ParseName"`
	Offsets []int   `opt:"..." required:"false" action:"ParseOffset"`
}

func (o *OptionStruct) ParseName(arg string) error {
	if arg == "invalid" {
		return fmt.Errorf("%q invalid value", arg)
	}
	o.Name = arg
	return nil
}

func (o *OptionStruct) ParseOffset(arg string) error {
	i, err := strconv.Atoi(arg)
	if err != nil {
		return err
	}
	o.Offsets = append(o.Offsets, i)
	return nil
}

func TestArgsToStructErrors(t *testing.T) {
	vectors := []struct {
		cmdline string
		err     string
	}{
		{"foo", "-j is required"},
		{"foo -j", "-j takes a value"},
		{"foo --delay -B", "--delay takes a value"},
		{"bar -j4", "<name> is required"},
		{"foo -j f", `strconv.ParseInt: parsing "f": invalid syntax.`},
		{"foo --delay=m", `strconv.ParseFloat: parsing "m": invalid syntax.`},
		{"foo -u8 hop", `-u unknown flag`},
		{"foo -z", `-z unknown flag`},
		{"bar -j4 invalid", `invalid value`},
		{"bar -j4 name name", `strconv.Atoi: parsing "name": invalid syntax.`},
	}

	for _, v := range vectors {
		t.Run(v.cmdline, func(t *testing.T) {
			err := opt.CmdlineToStruct(v.cmdline, new(OptionStruct))
			assert.ErrorContains(t, err, v.err)
		})
	}

	spec := opt.NewCmdSpec("bar", new(OptionStruct))
	assert.Equal(t, spec.Usage(), "bar -j <jobs> [--delay <delay>] [-B] <name> [<offsets>...]")
}

func TestArgsToStruct(t *testing.T) {
	vectors := []struct {
		cmdline  string
		expected OptionStruct
	}{
		{
			cmdline: `bar -j4 'f o o \(°</ f o o'`,
			expected: OptionStruct{
				Jobs:  4,
				Delay: 0.5,
				Name:  `f o o \(°</ f o o`,
			},
		},
		{
			cmdline: "foo --delay 0.1 -Bj 8",
			expected: OptionStruct{
				Jobs:    8,
				Delay:   0.1,
				Backoff: true,
			},
		},
		{
			cmdline: `baz -Bz --delay=0.1 -j8`,
			expected: OptionStruct{
				Jobs:    8,
				Delay:   0.1,
				Zero:    true,
				Backoff: true,
			},
		},
		{
			cmdline: `bar 'n a m e' -j7 --backoff`,
			expected: OptionStruct{
				Jobs:    7,
				Delay:   0.5,
				Backoff: true,
				Name:    "n a m e",
			},
		},
		{
			cmdline: `bar -j3 -- -j7`,
			expected: OptionStruct{
				Jobs:  3,
				Delay: 0.5,
				Name:  "-j7",
			},
		},
		{
			cmdline: `bar -j3 -- -j1 1 42`,
			expected: OptionStruct{
				Jobs:    3,
				Delay:   0.5,
				Name:    "-j1",
				Offsets: []int{1, 42},
			},
		},
	}

	for _, v := range vectors {
		t.Run(v.cmdline, func(t *testing.T) {
			var s OptionStruct
			assert.Nil(t, opt.CmdlineToStruct(v.cmdline, &s))
			assert.Equal(t, v.expected, s)
		})
	}
}
