package controlplane

import (
	"reflect"
	"strings"
	"testing"

	"github.com/nexuscrm/db"
	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

func requireComparable[T comparable]() {}

func TestRunnerRemainsComparable(t *testing.T) {
	requireComparable[Runner]()
}

func TestRunnerPublicShapeRemainsSourceCompatible(t *testing.T) {
	runnerType := reflect.TypeOf(Runner{})
	wantFields := []string{"DB", "Infra", "SvcMgr"}
	if runnerType.NumField() != len(wantFields) {
		t.Fatalf("Runner field count = %d, want %d", runnerType.NumField(), len(wantFields))
	}
	for index, want := range wantFields {
		if got := runnerType.Field(index).Name; got != want {
			t.Fatalf("Runner field %d = %q, want %q", index, got, want)
		}
	}
}

func TestRunnerOptionsPublicShapeRemainsSourceCompatible(t *testing.T) {
	optionsType := reflect.TypeOf(RunnerOptions{})
	if optionsType.NumField() != 1 || optionsType.Field(0).Name != "ComponentKindRegistrationSet" {
		t.Fatalf("RunnerOptions fields = %#v", reflect.VisibleFields(optionsType))
	}
}

func TestRunnerLiteralUsesEmptyRegistrationSet(t *testing.T) {
	runner := &Runner{}
	if got := runner.componentKindRegistrationSet().Registrations(); len(got) != 0 {
		t.Fatalf("literal runner registrations = %#v, want empty", got)
	}
}

func TestRunnerOwnsComponentKindRegistrationSet(t *testing.T) {
	set := runnerTestRegistrationSet(t)
	originalBuilder := BuildServiceGraph
	BuildServiceGraph = func(*database.TiDBConnection) (ServiceGraph, RunnerInfra, error) {
		return newTestServiceGraph(testServiceGraphConfig{}), RunnerInfra{}, nil
	}
	t.Cleanup(func() { BuildServiceGraph = originalBuilder })

	runner, err := NewRunnerWithOptions(nil, RunnerOptions{ComponentKindRegistrationSet: set})
	if err != nil {
		t.Fatal(err)
	}
	got := runner.componentKindRegistrationSet().Registrations()
	if len(got) != 1 || got[0].Contract.Kind != "runner_test" {
		t.Fatalf("runner registration set = %#v", got)
	}
}

func TestRunnerOptionsRejectNilServiceGraph(t *testing.T) {
	set := runnerTestRegistrationSet(t)
	originalBuilder := BuildServiceGraph
	BuildServiceGraph = func(*database.TiDBConnection) (ServiceGraph, RunnerInfra, error) {
		return nil, RunnerInfra{}, nil
	}
	t.Cleanup(func() { BuildServiceGraph = originalBuilder })

	_, err := NewRunnerWithOptions(nil, RunnerOptions{ComponentKindRegistrationSet: set})
	if err == nil || !strings.Contains(err.Error(), "nil service graph") {
		t.Fatalf("NewRunnerWithOptions error = %v, want nil service graph", err)
	}
}

func runnerTestRegistrationSet(t *testing.T) packs.ComponentKindRegistrationSet {
	t.Helper()
	set, err := packs.NewComponentKindRegistrationSet([]cp.ComponentKindRegistration{{
		Contract: cp.ComponentKindContract{Kind: "runner_test"},
		Capability: cp.SourceBackedComponentCapability{
			Kind: "runner_test", Installable: true,
		},
		BuildHandler: func(cp.ComponentBuildContext, cp.ComponentBuildRequest) error { return nil },
	}})
	if err != nil {
		t.Fatal(err)
	}
	return set
}
