package opt_test

import (
	"fmt"
	"testing"

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

type Cmd interface {
	Name() string
	Desc() string
}

type TopLevelStruct struct {
	Cmd Cmd `opt:":cmd:" action:"ParseCmd" complete:"CompleteCmd"`
}

type FooCmd struct {
	Fast bool `opt:"-f,--fast" desc:"Foo faster."`
}

type BarCmd struct {
	Location string `opt:"name" desc:"The location of this bar."`
}

type BazCmd struct {
	Debug uint `opt:"-d,--debug" desc:"Baz louder."`
}

func (f *FooCmd) Name() string { return "foo" }
func (f *BarCmd) Name() string { return "bar" }
func (f *BazCmd) Name() string { return "baz" }
func (f *FooCmd) Desc() string { return "Foo foo foo foo foo." }
func (f *BarCmd) Desc() string { return "Bar bar bar bar bar." }
func (f *BazCmd) Desc() string { return "Baz baz baz baz baz." }

var subcmds []Cmd = []Cmd{new(FooCmd), new(BarCmd), new(BazCmd)}

func (t *TopLevelStruct) ParseCmd(arg string) error {
	for _, cmd := range subcmds {
		if cmd.Name() == arg {
			t.Cmd = cmd
			return nil
		}
	}
	return fmt.Errorf("invalid subcommand %q", arg)
}

func (t *TopLevelStruct) CompleteCmd(arg string) []string {
	var comps []string
	for _, cmd := range subcmds {
		comps = append(comps, cmd.Name()+"\n"+cmd.Desc())
	}
	return comps
}

func TestParseSubCommand(t *testing.T) {
	var s TopLevelStruct

	spec := opt.NewCmdSpec("pouet", &s)
	assert.Equal(t, "pouet <cmd> ...", spec.Usage())

	err := opt.CmdlineToStruct("pouet ok prout", &s)
	assert.ErrorContains(t, err, `<cmd>: invalid subcommand "ok". Usage: pouet <cmd> ...`)

	err = opt.CmdlineToStruct("pouet foo -f", &s)
	assert.Nil(t, err)
	assert.NotNil(t, s.Cmd)
	assert.Equal(t, s.Cmd.Desc(), "Foo foo foo foo foo.")
	foo, ok := s.Cmd.(*FooCmd)
	assert.True(t, ok)
	assert.True(t, foo.Fast)

	s.Cmd = nil
	err = opt.CmdlineToStruct("pouet bar --ok", &s)
	assert.ErrorContains(t, err, `--ok unknown flag. Usage: pouet bar <name>`)

	err = opt.CmdlineToStruct("pouet bar houilles", &s)
	assert.Nil(t, err)
	assert.NotNil(t, s.Cmd)
	assert.Equal(t, s.Cmd.Desc(), "Bar bar bar bar bar.")
	bar, ok := s.Cmd.(*BarCmd)
	assert.True(t, ok)
	assert.Equal(t, "houilles", bar.Location)

	err = opt.CmdlineToStruct("pouet baz -d 9000", &s)
	assert.Nil(t, err)
	assert.NotNil(t, s.Cmd)
	assert.Equal(t, s.Cmd.Desc(), "Baz baz baz baz baz.")
	baz, ok := s.Cmd.(*BazCmd)
	assert.True(t, ok)
	assert.Equal(t, uint(9000), baz.Debug)
}

func TestCompleteSubCommand(t *testing.T) {
	var s TopLevelStruct
	spec := opt.NewCmdSpec("pouet", &s)
	comps, _ := spec.GetCompletions(opt.LexArgs("pouet "))
	assert.Equal(t, 3, len(comps))
	assert.Equal(t, "foo", comps[0].Value)
	assert.Equal(t, "Foo foo foo foo foo.", comps[0].Description)
	assert.Equal(t, "bar", comps[1].Value)
	assert.Equal(t, "Bar bar bar bar bar.", comps[1].Description)
	assert.Equal(t, "baz", comps[2].Value)
	assert.Equal(t, "Baz baz baz baz baz.", comps[2].Description)

	comps, _ = spec.GetCompletions(opt.LexArgs("pouet foo "))
	assert.Equal(t, 2, len(comps))
	assert.Equal(t, "-f ", comps[0].Value)
	assert.Equal(t, "Foo faster.", comps[0].Description)
	assert.Equal(t, "--fast ", comps[1].Value)
	assert.Equal(t, "Foo faster.", comps[1].Description)
}
