// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package jobspec

import (
	"fmt"

	multierror "github.com/hashicorp/go-multierror"
	"github.com/hashicorp/hcl"
	"github.com/hashicorp/hcl/hcl/ast"
	"github.com/hashicorp/nomad/api"
	"github.com/mitchellh/mapstructure"
)

func parseMultiregion(result *api.Multiregion, list *ast.ObjectList) error {

	list = list.Elem()
	if len(list.Items) > 1 {
		return fmt.Errorf("only one 'multiregion' block allowed")
	}
	if len(list.Items) == 0 {
		return nil
	}

	// Get our multiregion object and decode it
	obj := list.Items[0]
	var m map[string]interface{}
	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
		return err
	}

	// Value should be an object
	var listVal *ast.ObjectList
	if ot, ok := obj.Val.(*ast.ObjectType); ok {
		listVal = ot.List
	} else {
		return fmt.Errorf("multiregion should be an object")
	}

	// Check for invalid keys
	valid := []string{
		"strategy",
		"region",
	}
	if err := checkHCLKeys(obj.Val, valid); err != nil {
		return err
	}

	// If we have a strategy, then parse that
	if o := listVal.Filter("strategy"); len(o.Items) > 0 {
		if err := parseMultiregionStrategy(&result.Strategy, o); err != nil {
			return multierror.Prefix(err, "strategy ->")
		}
	}
	// If we have regions, then parse those
	if o := listVal.Filter("region"); len(o.Items) > 0 {
		if err := parseMultiregionRegions(result, o); err != nil {
			return multierror.Prefix(err, "regions ->")
		}
	} else {
		return fmt.Errorf("'multiregion' requires one or more 'region' blocks")
	}
	return nil
}

func parseMultiregionStrategy(final **api.MultiregionStrategy, list *ast.ObjectList) error {
	list = list.Elem()
	if len(list.Items) > 1 {
		return fmt.Errorf("only one 'strategy' block allowed")
	}

	// Get our job object
	obj := list.Items[0]

	// Check for invalid keys
	valid := []string{
		"max_parallel",
		"on_failure",
	}
	if err := checkHCLKeys(obj.Val, valid); err != nil {
		return err
	}

	var m map[string]interface{}
	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
		return err
	}

	var result api.MultiregionStrategy
	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
		WeaklyTypedInput: true,
		Result:           &result,
	})

	if err != nil {
		return err
	}
	if err := dec.Decode(m); err != nil {
		return err
	}
	*final = &result
	return nil
}

func parseMultiregionRegions(result *api.Multiregion, list *ast.ObjectList) error {
	list = list.Children()
	if len(list.Items) == 0 {
		return nil
	}

	// Go through each object and turn it into an actual result.
	collection := make([]*api.MultiregionRegion, 0, len(list.Items))
	seen := make(map[string]struct{})
	for _, item := range list.Items {
		n := item.Keys[0].Token.Value().(string)

		// Make sure we haven't already found this
		if _, ok := seen[n]; ok {
			return fmt.Errorf("region '%s' defined more than once", n)
		}
		seen[n] = struct{}{}

		// We need this later
		var listVal *ast.ObjectList
		if ot, ok := item.Val.(*ast.ObjectType); ok {
			listVal = ot.List
		} else {
			return fmt.Errorf("region '%s': should be an object", n)
		}

		// Check for invalid keys
		valid := []string{
			"count",
			"datacenters",
			"meta",
		}
		if err := checkHCLKeys(listVal, valid); err != nil {
			return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
		}

		var m map[string]interface{}
		if err := hcl.DecodeObject(&m, item.Val); err != nil {
			return err
		}

		// Build the region with the basic decode
		var r api.MultiregionRegion
		r.Name = n
		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
			WeaklyTypedInput: true,
			Result:           &r,
		})
		if err != nil {
			return err
		}
		if err := dec.Decode(m); err != nil {
			return err
		}
		collection = append(collection, &r)
	}

	result.Regions = append(result.Regions, collection...)
	return nil
}
