// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

//go:generate pluginator
package main

import (
	"fmt"

	"github.com/evanphx/json-patch"
	"github.com/pkg/errors"
	"sigs.k8s.io/kustomize/api/resmap"
	"sigs.k8s.io/kustomize/api/resource"
	"sigs.k8s.io/kustomize/api/types"
	"sigs.k8s.io/yaml"
)

type plugin struct {
	loadedPatch  *resource.Resource
	decodedPatch jsonpatch.Patch
	Path         string          `json:"path,omitempty" yaml:"path,omitempty"`
	Patch        string          `json:"patch,omitempty" yaml:"patch,omitempty"`
	Target       *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
}

//noinspection GoUnusedGlobalVariable
var KustomizePlugin plugin

func (p *plugin) Config(
	h *resmap.PluginHelpers, c []byte) (err error) {
	err = yaml.Unmarshal(c, p)
	if err != nil {
		return err
	}
	if p.Patch == "" && p.Path == "" {
		err = fmt.Errorf(
			"must specify one of patch and path in\n%s", string(c))
		return
	}
	if p.Patch != "" && p.Path != "" {
		err = fmt.Errorf(
			"patch and path can't be set at the same time\n%s", string(c))
		return
	}
	var in []byte
	if p.Path != "" {
		in, err = h.Loader().Load(p.Path)
		if err != nil {
			return
		}
	}
	if p.Patch != "" {
		in = []byte(p.Patch)
	}

	patchSM, errSM := h.ResmapFactory().RF().FromBytes(in)
	patchJson, errJson := jsonPatchFromBytes(in)
	if errSM != nil && errJson != nil {
		err = fmt.Errorf(
			"unable to get either a Strategic Merge Patch or JSON patch 6902 from %s", p.Patch)
		return
	}
	if errSM == nil && errJson != nil {
		p.loadedPatch = patchSM
	}
	if errJson == nil && errSM != nil {
		p.decodedPatch = patchJson
	}
	if patchSM != nil && patchJson != nil {
		err = fmt.Errorf(
			"a patch can't be both a Strategic Merge Patch and JSON patch 6902 %s", p.Patch)
	}

	return nil
}

func (p *plugin) Transform(m resmap.ResMap) error {
	if p.loadedPatch != nil && p.Target == nil {
		target, err := m.GetById(p.loadedPatch.OrgId())
		if err != nil {
			return err
		}
		err = target.Patch(p.loadedPatch.Kunstructured)
		if err != nil {
			return err
		}
		return nil
	}

	if p.Target == nil {
		return fmt.Errorf("must specify a target for patch %s", p.Patch)
	}

	resources, err := m.Select(*p.Target)
	if err != nil {
		return err
	}
	for _, res := range resources {
		if p.decodedPatch != nil {
			rawObj, err := res.MarshalJSON()
			if err != nil {
				return err
			}
			modifiedObj, err := p.decodedPatch.Apply(rawObj)
			if err != nil {
				return errors.Wrapf(
					err, "failed to apply json patch '%s'", p.Patch)
			}
			err = res.UnmarshalJSON(modifiedObj)
			if err != nil {
				return err
			}
		}
		if p.loadedPatch != nil {
			patchCopy := p.loadedPatch.DeepCopy()
			patchCopy.SetName(res.GetName())
			patchCopy.SetNamespace(res.GetNamespace())
			patchCopy.SetGvk(res.GetGvk())
			err = res.Patch(patchCopy.Kunstructured)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// jsonPatchFromBytes loads a Json 6902 patch from
// a bytes input
func jsonPatchFromBytes(
	in []byte) (jsonpatch.Patch, error) {
	ops := string(in)
	if ops == "" {
		return nil, fmt.Errorf("empty json patch operations")
	}

	if ops[0] != '[' {
		jsonOps, err := yaml.YAMLToJSON(in)
		if err != nil {
			return nil, err
		}
		ops = string(jsonOps)
	}
	return jsonpatch.DecodePatch([]byte(ops))
}
