/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package validators

import (
	"fmt"
	"strings"
)

type ValidationError struct {
	Violations []Violation `json:"violations"`
}

type Violation struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

// OK returns and empty validation error (i.e. success).
func OK() ValidationError {
	return ValidationError{}
}

func (v *ValidationError) Error() string {
	msg := ""
	for _, violation := range v.Violations {
		if msg != "" {
			msg = fmt.Sprintf("%s; %s: %s", msg, violation.Field, violation.Message)
		} else {
			msg += fmt.Sprintf("%s: %s", violation.Field, violation.Message)
		}
	}
	return msg
}

func (v *ValidationError) HasViolations() bool {
	return len(v.Violations) > 0
}

func (v *ValidationError) OrNil() error {
	if v.HasViolations() {
		return v
	}
	return nil
}

func (v *ValidationError) AddViolationAt(path PathBuilder, message string) {
	v.AddViolation(path.String(), message)
}

func (v *ValidationError) AddViolation(field string, message string) {
	violation := Violation{
		Field:   field,
		Message: message,
	}
	v.Violations = append(v.Violations, violation)
}

func (v *ValidationError) AddErrorAt(path PathBuilder, validationErr ValidationError) {
	for _, violation := range validationErr.Violations {
		field := Root()
		if violation.Field != "" {
			field = RootedAt(violation.Field)
		}
		newViolation := Violation{
			Field:   path.concat(field).String(),
			Message: violation.Message,
		}
		v.Violations = append(v.Violations, newViolation)
	}
}

func (v *ValidationError) Add(err ValidationError) {
	v.AddErrorAt(Root(), err)
}

func (v *ValidationError) AddError(rootField string, validationErr ValidationError) {
	root := Root()
	if rootField != "" {
		root = RootedAt(rootField)
	}
	v.AddErrorAt(root, validationErr)
}

// Transform returns a new ValidationError with every violation
// transformed by a given transformFunc.
func (v *ValidationError) Transform(transformFunc func(Violation) Violation) *ValidationError {
	if v == nil {
		return nil
	}
	if transformFunc == nil || len(v.Violations) == 0 {
		rv := *v
		return &rv
	}
	result := ValidationError{
		Violations: make([]Violation, len(v.Violations)),
	}
	for i := range v.Violations {
		result.Violations[i] = transformFunc(v.Violations[i])
	}
	return &result
}

func MakeUnimplementedFieldErr(path PathBuilder) ValidationError {
	var err ValidationError
	err.AddViolationAt(path, "field is not implemented")
	return err
}

func MakeRequiredFieldErr(path PathBuilder) ValidationError {
	var err ValidationError
	err.AddViolationAt(path, "cannot be empty")
	return err
}

func MakeOneOfErr(fieldA, fieldB, msg string, oneOf []string) ValidationError {
	var err ValidationError
	var quoted []string

	for _, value := range oneOf {
		quoted = append(quoted, fmt.Sprintf("%q", value))
	}

	message := fmt.Sprintf(
		"%q %s one of [%s]",
		fieldA,
		msg,
		strings.Join(quoted, ", "),
	)

	if fieldB != "" {
		message = fmt.Sprintf(
			"%q %s when %q is one of [%s]",
			fieldA,
			msg,
			fieldB,
			strings.Join(quoted, ", "),
		)
	}

	err.AddViolationAt(Root(), message)

	return err
}

func MakeFieldMustBeOneOfErr(field string, allowed ...string) ValidationError {
	return MakeOneOfErr(field, "", "must be", allowed)
}

func IsValidationError(err error) bool {
	_, ok := err.(*ValidationError)
	return ok
}

type PathBuilder []string

func RootedAt(name string) PathBuilder {
	return PathBuilder{name}
}

func Root() PathBuilder {
	return PathBuilder{}
}

func (p PathBuilder) Field(name string) PathBuilder {
	element := name
	if len(p) > 0 {
		element = fmt.Sprintf(".%s", element)
	}
	return append(p, element)
}

func (p PathBuilder) Index(index int) PathBuilder {
	return append(p, fmt.Sprintf("[%d]", index))
}

func (p PathBuilder) Key(key string) PathBuilder {
	return append(p, fmt.Sprintf("[%q]", key))
}

func (p PathBuilder) String() string {
	return strings.Join(p, "")
}

func (p PathBuilder) concat(other PathBuilder) PathBuilder {
	if len(other) == 0 {
		return p
	}
	if len(p) == 0 {
		return other
	}

	firstOther := other[0]
	if !strings.HasPrefix(firstOther, "[") {
		firstOther = "." + firstOther
	}

	return append(append(p, firstOther), other[1:]...)
}
