package diff

import (
	"context"

	"github.com/go-logr/logr"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/util/managedfields"
	"k8s.io/klog/v2/textlogger"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
)

type Option func(*options)

// Holds diffing settings
type options struct {
	// If set to true then differences caused by aggregated roles in RBAC resources are ignored.
	ignoreAggregatedRoles bool
	normalizer            Normalizer
	skipFullNormalize     bool
	log                   logr.Logger
	structuredMergeDiff   bool
	gvkParser             *managedfields.GvkParser
	manager               string
	serverSideDiff        bool
	serverSideDryRunner   ServerSideDryRunner
	ignoreMutationWebhook bool
}

func applyOptions(opts []Option) options {
	o := options{
		ignoreAggregatedRoles: false,
		ignoreMutationWebhook: true,
		normalizer:            GetNoopNormalizer(),
		skipFullNormalize:     false,
		log:                   textlogger.NewLogger(textlogger.NewConfig()),
	}
	for _, opt := range opts {
		opt(&o)
	}
	return o
}

type KubeApplier interface {
	ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate, serverSideApply bool, manager string) (string, error)
}

// ServerSideDryRunner defines the contract to run a server-side apply in
// dryrun mode.
type ServerSideDryRunner interface {
	Run(ctx context.Context, obj *unstructured.Unstructured, manager string) (string, error)
}

// K8sServerSideDryRunner is the Kubernetes implementation of ServerSideDryRunner.
type K8sServerSideDryRunner struct {
	dryrunApplier KubeApplier
}

// NewK8sServerSideDryRunner will instantiate a new K8sServerSideDryRunner with
// the given kubeApplier.
func NewK8sServerSideDryRunner(kubeApplier KubeApplier) *K8sServerSideDryRunner {
	return &K8sServerSideDryRunner{
		dryrunApplier: kubeApplier,
	}
}

// ServerSideApplyDryRun will invoke a kubernetes server-side apply with the given
// obj and the given manager in dryrun mode. Will return the predicted live state
// json as string.
func (kdr *K8sServerSideDryRunner) Run(ctx context.Context, obj *unstructured.Unstructured, manager string) (string, error) {
	//nolint:wrapcheck // trivial function, don't bother wrapping
	return kdr.dryrunApplier.ApplyResource(ctx, obj, cmdutil.DryRunServer, false, false, true, manager)
}

func IgnoreAggregatedRoles(ignore bool) Option {
	return func(o *options) {
		o.ignoreAggregatedRoles = ignore
	}
}

func WithNormalizer(normalizer Normalizer) Option {
	return func(o *options) {
		o.normalizer = normalizer
	}
}

func WithSkipFullNormalize(skip bool) Option {
	return func(o *options) {
		o.skipFullNormalize = skip
	}
}

func WithLogr(log logr.Logger) Option {
	return func(o *options) {
		o.log = log
	}
}

func WithStructuredMergeDiff(smd bool) Option {
	return func(o *options) {
		o.structuredMergeDiff = smd
	}
}

func WithGVKParser(parser *managedfields.GvkParser) Option {
	return func(o *options) {
		o.gvkParser = parser
	}
}

func WithManager(manager string) Option {
	return func(o *options) {
		o.manager = manager
	}
}

func WithServerSideDiff(ssd bool) Option {
	return func(o *options) {
		o.serverSideDiff = ssd
	}
}

func WithIgnoreMutationWebhook(mw bool) Option {
	return func(o *options) {
		o.ignoreMutationWebhook = mw
	}
}

func WithServerSideDryRunner(ssadr ServerSideDryRunner) Option {
	return func(o *options) {
		o.serverSideDryRunner = ssadr
	}
}
