package wand

import (
	"code.squareroundforest.org/arpio/bind"
	"errors"
	"fmt"
	"reflect"
	"regexp"
	"strings"
)

var commandNameExpression = regexp.MustCompile("^[a-zA-Z_][a-zA-Z_0-9-]*$")

func wrap(impl any) Cmd {
	cmd, ok := impl.(Cmd)
	if ok {
		return cmd
	}

	return Command("", impl)
}

func validateFields(f map[string][]bind.Field, conf Config) error {
	hasConfigFromOption := hasConfigFromOption(conf)
	for name, ff := range f {
		var t []bind.FieldType
		for _, fi := range ff {
			t = append(t, fi.Type())
		}

		if !compatibleTypes(t...) {
			return fmt.Errorf("duplicate fields with different types: %s", name)
		}

		if hasConfigFromOption && name == "config" {
			return errors.New("option reserved for config file shadowed by struct field")
		}
	}

	return nil
}

func validatePositional(p []reflect.Type, variadic bool, min, max int) error {
	fixedPositional := len(p)
	if variadic {
		fixedPositional--
	}

	if min > 0 && min < fixedPositional {
		return fmt.Errorf(
			"minimum positional arguments defined as %d but the implementation expects minimum %d fixed parameters",
			min,
			fixedPositional,
		)
	}

	if min > 0 && min > fixedPositional && !variadic {
		return fmt.Errorf(
			"minimum positional arguments defined as %d but the implementation has only %d fixed parameters and no variadic parameter",
			min,
			fixedPositional,
		)
	}

	if max > 0 && max < fixedPositional {
		return fmt.Errorf(
			"maximum positional arguments defined as %d but the implementation expects minimum %d fixed parameters",
			max,
			fixedPositional,
		)
	}

	if min > 0 && max > 0 && min > max {
		return fmt.Errorf(
			"minimum positional arguments defined as larger then the maxmimum positional: %d > %d",
			min,
			max,
		)
	}

	return nil
}

func validateIOParameters(ior, iow []reflect.Type) error {
	if len(ior) > 1 || len(iow) > 1 {
		return errors.New("only zero or one reader and zero or one writer parameter is supported")
	}

	return nil
}

func validateImpl(cmd Cmd, conf Config) error {
	if !isFunc(cmd.impl) {
		return errors.New("command implementation must be a function or a pointer to a function")
	}

	p := parameters(cmd.impl)
	for _, pi := range p {
		if !isReader(pi) && !isWriter(pi) && !bindable(pi) {
			return fmt.Errorf("unsupported parameter type: %s", pi.Name())
		}
	}

	f := mapFields(cmd.impl)
	if err := validateFields(f, conf); err != nil {
		return err
	}

	pos, variadic := positional(cmd.impl)
	if err := validatePositional(pos, variadic, cmd.minPositional, cmd.maxPositional); err != nil {
		return err
	}

	ior, iow := ioParameters(cmd.impl)
	if err := validateIOParameters(ior, iow); err != nil {
		return err
	}

	return nil
}

func validateCommandTree(cmd Cmd, conf Config) error {
	if cmd.version != "" {
		return nil
	}

	if cmd.impl == nil && !cmd.group {
		return fmt.Errorf("command does not have an implementation: %s", cmd.name)
	}

	if cmd.impl == nil && len(cmd.subcommands) == 0 {
		return fmt.Errorf("empty command category: %s", cmd.name)
	}

	if cmd.impl != nil {
		if err := validateImpl(cmd, conf); err != nil {
			return fmt.Errorf("%s: %w", cmd.name, err)
		}
	}

	var hasDefault bool
	names := make(map[string]bool)
	for _, s := range cmd.subcommands {
		if !commandNameExpression.MatchString(s.name) {
			return fmt.Errorf("command name is not a valid symbol: '%s'", cmd.name)
		}

		if names[s.name] {
			return fmt.Errorf("subcommand name conflict: %s", s.name)
		}

		names[s.name] = true
		if s.isDefault && cmd.impl != nil {
			return fmt.Errorf(
				"default subcommand defined for a command that has an explicit implementation: %s, %s",
				cmd.name,
				s.name,
			)
		}

		if s.isDefault && hasDefault {
			return fmt.Errorf("multiple default subcommands for: %s", cmd.name)
		}

		if s.isDefault {
			hasDefault = true
		}

		if err := validateCommandTree(s, conf); err != nil {
			return fmt.Errorf("%s: %w", cmd.name, err)
		}
	}

	return nil
}

func checkShortFormDefinition(existing map[string]string, short, long string) error {
	e, ok := existing[short]
	if !ok {
		return nil
	}

	if e == long {
		return nil
	}

	return fmt.Errorf(
		"using the same short form for different options is not allowed: %s->%s, %s->%s",
		short, long, short, e,
	)
}

func collectMappedShortForms(to, from map[string]string) (map[string]string, error) {
	for s, l := range from {
		if err := checkShortFormDefinition(to, s, l); err != nil {
			return nil, err
		}

		if to == nil {
			to = make(map[string]string)
		}

		to[s] = l
	}

	return to, nil
}

func validateShortFormsTree(cmd Cmd) (map[string]string, map[string]string, error) {
	var mapped, unmapped map[string]string
	for _, sc := range cmd.subcommands {
		m, um, err := validateShortFormsTree(sc)
		if err != nil {
			return nil, nil, err
		}

		if mapped, err = collectMappedShortForms(mapped, m); err != nil {
			return nil, nil, err
		}

		if unmapped, err = collectMappedShortForms(unmapped, um); err != nil {
			return nil, nil, err
		}
	}

	mf := mapFields(cmd.impl)
	_, helpDefined := mf["help"]
	for i := 0; i < len(cmd.shortForms); i += 2 {
		s, l := cmd.shortForms[i], cmd.shortForms[i+1]
		if s == "h" && l == "help" && !helpDefined {
			continue
		}

		r := []rune(s)
		if len(r) != 1 || r[0] < 'a' || r[0] > 'z' {
			return nil, nil, fmt.Errorf("invalid short form: %s", s)
		}

		if err := checkShortFormDefinition(mapped, s, l); err != nil {
			return nil, nil, err
		}

		if err := checkShortFormDefinition(unmapped, s, l); err != nil {
			return nil, nil, err
		}

		_, hasField := mf[l]
		_, isMapped := mapped[s]
		if !hasField && !isMapped {
			if unmapped == nil {
				unmapped = make(map[string]string)
			}

			unmapped[s] = l
			continue
		}

		if mapped == nil {
			mapped = make(map[string]string)
		}

		delete(unmapped, s)
		mapped[s] = l
	}

	return mapped, unmapped, nil
}

func validateShortForms(cmd Cmd) error {
	_, um, err := validateShortFormsTree(cmd)
	if err != nil {
		return err
	}

	if len(um) != 0 {
		var umn []string
		for n := range um {
			umn = append(umn, n)
		}

		return fmt.Errorf("unmapped short forms: %s", strings.Join(umn, ", "))
	}

	return nil
}

func validateCommand(cmd Cmd, conf Config) error {
	if err := validateCommandTree(cmd, conf); err != nil {
		return err
	}

	if err := validateShortForms(cmd); err != nil {
		return err
	}

	return nil
}

func boolOptions(cmd Cmd) []string {
	f := fields(cmd.impl)
	b := boolFields(f)

	var n []string
	for _, fi := range b {
		n = append(n, fi.Name())
	}

	var hasHelp bool
	for _, fi := range f {
		if fi.Name() == "help" {
			hasHelp = true
			break
		}
	}

	if !hasHelp {
		n = append(n, "help")
	}

	sfm := make(map[string][]string)
	for i := 0; i < len(cmd.shortForms); i += 2 {
		s, l := cmd.shortForms[i], cmd.shortForms[i+1]
		sfm[l] = append(sfm[l], s)
	}

	var sf []string
	for _, ni := range n {
		sf = append(sf, sfm[ni]...)
	}

	var hasHelpShortForm bool
	for i := 0; i < len(cmd.shortForms); i += 2 {
		if cmd.shortForms[i] == "h" {
			hasHelpShortForm = true
			break
		}
	}

	if !hasHelp && !hasHelpShortForm {
		sf = append(sf, "h")
	}

	return append(n, sf...)
}
