package wand

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

func validateKeyValues(cmd Cmd, keyValues map[string][]string, originalNames map[string]string) error {
	mf := mapFields(cmd.impl)
	for name, values := range keyValues {
		f, ok := mf[name]
		if !ok {
			continue
		}

		for _, fi := range f {
			if len(values) > 1 && !fi.List() {
				return fmt.Errorf(
					"expected only one value, received %d, for %s",
					len(values),
					originalNames[name],
				)
			}

			for _, v := range values {
				if !canScan(fi.Type(), v) {
					return fmt.Errorf("type mismatch: %s", originalNames[name])
				}
			}
		}
	}

	return nil
}

func validateConfig(cmd Cmd, c config) error {
	if err := validateKeyValues(cmd, c.values, c.originalNames); err != nil {
		return fmt.Errorf("config: %w", err)
	}

	return nil
}

func validateEnv(cmd Cmd, e env) error {
	if err := validateKeyValues(cmd, e.values, e.originalNames); err != nil {
		return fmt.Errorf("environment: %w", err)
	}

	return nil
}

func validateOptions(cmd Cmd, o []option, conf Config) error {
	ms := make(map[string]string)
	ml := make(map[string]string)
	for i := 0; i < len(cmd.shortForms); i += 2 {
		s, l := cmd.shortForms[i], cmd.shortForms[i+1]
		ms[s] = l
		ml[l] = s
	}

	var names []string
	mo := make(map[string][]option)
	for _, oi := range o {
		n := oi.name
		if oi.shortForm {
			ln, ok := ms[n]
			if !ok {
				return fmt.Errorf("option not supported: -%s", n)
			}

			n = ln
		}

		names = append(names, n)
		mo[n] = append(mo[n], oi)
	}

	hasConfigOption := hasConfigFromOption(conf)
	mf := mapFields(cmd.impl)
	for _, n := range names {
		os := mo[n]
		en := "--" + n
		if sn, ok := ml[n]; ok {
			en += ", -" + sn
		}

		if hasConfigOption && n == "config" {
			continue
		}

		f := mf[n]
		if len(f) == 0 {
			return fmt.Errorf("option not supported: %s", en)
		}

		for _, fi := range f {
			if len(os) > 1 && !fi.List() {
				return fmt.Errorf(
					"expected only one value, received %d, as option, %s",
					len(os),
					en,
				)
			}

			for _, oi := range os {
				if oi.value.isBool && fi.Type() != bind.Bool {
					return fmt.Errorf(
						"received boolean value for field that does not accept it: %s",
						en,
					)
				}

				if !oi.value.isBool && !canScan(fi.Type(), oi.value.str) {
					return fmt.Errorf(
						"option cannot be applied, type mismatch: %s",
						en,
					)
				}
			}
		}
	}

	return nil
}

func validatePositionalArgs(cmd Cmd, a []string) error {
	p, variadic := positional(cmd.impl)
	min := len(p)
	max := len(p)
	if variadic {
		min--
		max = -1
	}

	if cmd.minPositional > min {
		min = cmd.minPositional
	}

	if cmd.maxPositional > 0 {
		max = cmd.maxPositional
	}

	if len(a) < min {
		return fmt.Errorf("not enough positional arguments, expected minimum %d", min)
	}

	if max >= 0 && len(a) > max {
		return fmt.Errorf("too many positional arguments, expected maximum %d", max)
	}

	for i, ai := range a {
		var pi reflect.Type
		if i >= len(p) {
			pi = p[len(p)-1]
		} else {
			pi = p[i]
		}

		if !canScanType(pi, ai) {
			return fmt.Errorf(
				"cannot apply positional argument at index %d, expecting %s",
				i,
				fmt.Sprint(pi.Kind()),
			)
		}
	}

	return nil
}

func validateInput(cmd Cmd, conf Config, c config, e env, cl commandLine) error {
	if err := validateConfig(cmd, c); err != nil {
		return err
	}

	if err := validateEnv(cmd, e); err != nil {
		return err
	}

	if err := validateOptions(cmd, cl.options, conf); err != nil {
		return err
	}

	if err := validatePositionalArgs(cmd, cl.positional); err != nil {
		return err
	}

	return nil
}
