// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package controllertest

import (
	svc "github.com/hashicorp/consul/agent/grpc-external/services/resource"
	svctest "github.com/hashicorp/consul/agent/grpc-external/services/resource/testing"
	"github.com/hashicorp/consul/internal/controller"
	"github.com/hashicorp/consul/internal/resource"
	"github.com/hashicorp/consul/proto-public/pbresource"
	"github.com/hashicorp/consul/sdk/testutil"
)

type Builder struct {
	serviceBuilder        *svctest.Builder
	controllerRegisterFns []func(*controller.Manager)
}

// NewControllerTestBuilder starts to build out out the necessary controller testing
// runtime for lightweight controller integration testing. This will run a single
// in-memory resource service instance and the controller manager. Usage of this
// builder is an easy way to ensure that all the right resource gRPC connections/clients
// get set up appropriately in a manner identical to how they would be on a full
// running server.
func NewControllerTestBuilder() *Builder {
	return &Builder{
		// disable cloning because we will enable it after passing the non-cloning variant
		// to the controller manager.
		serviceBuilder: svctest.NewResourceServiceBuilder().WithCloningDisabled(),
	}
}

// WithV2Tenancy tells the internally created resource service to use V2 tenancy.
func (b *Builder) WithV2Tenancy(useV2Tenancy bool) *Builder {
	b.serviceBuilder = b.serviceBuilder.WithV2Tenancy(useV2Tenancy)
	return b
}

// Registry retrieves the resource registry from the internal im-mem resource service.
func (b *Builder) Registry() resource.Registry {
	return b.serviceBuilder.Registry()
}

// WithResourceRegisterFns allows configuring functions to be run to register resource
// types with the internal in-mem resource service for the duration of the test.
func (b *Builder) WithResourceRegisterFns(registerFns ...func(resource.Registry)) *Builder {
	b.serviceBuilder = b.serviceBuilder.WithRegisterFns(registerFns...)
	return b
}

// WithControllerRegisterFns allows configuring a set of controllers that should be registered
// with the controller manager and executed during Run.
func (b *Builder) WithControllerRegisterFns(registerFns ...func(*controller.Manager)) *Builder {
	for _, registerFn := range registerFns {
		b.controllerRegisterFns = append(b.controllerRegisterFns, registerFn)
	}
	return b
}

// WithACLResolver is used to provide an ACLResolver implementation to the internal resource service.
func (b *Builder) WithACLResolver(aclResolver svc.ACLResolver) *Builder {
	b.serviceBuilder = b.serviceBuilder.WithACLResolver(aclResolver)
	return b
}

// WithTenancies adds additional tenancies if default/default is not sufficient.
func (b *Builder) WithTenancies(tenancies ...*pbresource.Tenancy) *Builder {
	b.serviceBuilder = b.serviceBuilder.WithTenancies(tenancies...)
	return b
}

// Run executes both the internal resource service and the controller manager.
// The controller manager gets told it is the Raft leader so all controllers
// will get executed. The resource service, controller manager and all controllers
// will be stopped when the test finishes by registering a cleanup method on
// the test.
func (b *Builder) Run(t testutil.TestingTB) pbresource.ResourceServiceClient {
	t.Helper()

	ctx := testutil.TestContext(t)

	client := b.serviceBuilder.Run(t)

	mgr := controller.NewManager(client, testutil.Logger(t))
	for _, register := range b.controllerRegisterFns {
		register(mgr)
	}

	mgr.SetRaftLeader(true)
	go mgr.Run(ctx)

	// auto-clone messages going through the client so that test
	// code is free to modify objects in place without cloning themselves.
	return pbresource.NewCloningResourceServiceClient(client)
}
