package hook

import (
	"regexp"

	rspec "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
)

// When holds hook-injection conditions.
type When struct {
	Always        *bool             `json:"always,omitempty"`
	Annotations   map[string]string `json:"annotations,omitempty"`
	Commands      []string          `json:"commands,omitempty"`
	HasBindMounts *bool             `json:"hasBindMounts,omitempty"`

	// Or enables any-of matching.
	//
	// Deprecated: this property is for is backwards-compatibility with
	// 0.1.0 hooks.  It will be removed when we drop support for them.
	Or bool `json:"-"`
}

// Match returns true if the given conditions match the configuration.
func (when *When) Match(config *rspec.Spec, annotations map[string]string, hasBindMounts bool) (match bool, err error) {
	matches := 0

	if when.Always != nil {
		if *when.Always {
			if when.Or {
				return true, nil
			}
			matches++
		} else if !when.Or {
			return false, nil
		}
	}

	if when.HasBindMounts != nil {
		if *when.HasBindMounts && hasBindMounts {
			if when.Or {
				return true, nil
			}
			matches++
		} else if !when.Or {
			return false, nil
		}
	}

	for keyPattern, valuePattern := range when.Annotations {
		match := false
		for key, value := range annotations {
			match, err = regexp.MatchString(keyPattern, key)
			if err != nil {
				return false, errors.Wrap(err, "annotation key")
			}
			if match {
				match, err = regexp.MatchString(valuePattern, value)
				if err != nil {
					return false, errors.Wrap(err, "annotation value")
				}
				if match {
					break
				}
			}
		}
		if match {
			if when.Or {
				return true, nil
			}
			matches++
		} else if !when.Or {
			return false, nil
		}
	}

	if config.Process != nil && len(when.Commands) > 0 {
		if len(config.Process.Args) == 0 {
			return false, errors.New("process.args must have at least one entry")
		}
		command := config.Process.Args[0]
		for _, cmdPattern := range when.Commands {
			match, err := regexp.MatchString(cmdPattern, command)
			if err != nil {
				return false, errors.Wrap(err, "command")
			}
			if match {
				return true, nil
			}
		}
		return false, nil
	}

	return matches > 0, nil
}
