package k8scliv1

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/assert"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	"github.com/tidbcloud/observability/agent-control-server/pkg/loader"
	"github.com/tidbcloud/observability/agent-control-server/pkg/testkits"
)

type getClientTestCase struct {
	name    string
	cluster client.Object
	wantErr bool
	errMsg  string
}

func runGetClientTests(t *testing.T, tests []getClientTestCase, clusterName string, newManager func(client.Client, client.Options) ClientManager) {
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var objs []client.Object
			if tt.cluster != nil {
				objs = append(objs, tt.cluster)
			}

			fakeCli := fake.NewClientBuilder().WithScheme(loader.ProviderScheme()).WithObjects(objs...).Build()
			manager := newManager(fakeCli, client.Options{})

			key := client.ObjectKey{Name: clusterName}
			if tt.cluster == nil {
				key = client.ObjectKey{Name: "non-existent"}
			}

			cli, err := manager.GetClient(context.Background(), key)
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				assert.Nil(t, cli)
				return
			}

			if err != nil {
				assert.Contains(t, err.Error(), "exec plugin")
				return
			}
			assert.NotNil(t, cli)
		})
	}
}

type getConfigTestCase struct {
	name           string
	cluster        client.Object
	setupResources []client.Object
	wantErr        bool
	errMsg         string
	verify         func(*testing.T, *rest.Config)
}

func runGetConfigTests(t *testing.T, tests []getConfigTestCase, clusterName string, newManager func(client.Client, client.Options) ClientManager) {
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var objs []client.Object
			if tt.cluster != nil {
				objs = append(objs, tt.cluster)
			}
			objs = append(objs, tt.setupResources...)

			fakeCli := fake.NewClientBuilder().WithScheme(loader.ProviderScheme()).WithObjects(objs...).Build()
			manager := newManager(fakeCli, client.Options{})

			key := client.ObjectKey{Name: clusterName}
			if tt.cluster == nil {
				key = client.ObjectKey{Name: "non-existent"}
			}

			cfg, err := manager.GetConfig(context.Background(), key)
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				return
			}

			assert.NoError(t, err)
			assert.NotNil(t, cfg)

			if tt.verify != nil {
				tt.verify(t, cfg)
			}
		})
	}
}

func TestClientGetter_GetClient(t *testing.T) {
	tests := []struct {
		name         string
		injectedPath string
		manager      *testkits.ClientManager
		wantErr      bool
		errMsg       string
	}{
		{
			name:         "returns client from injected kubeconfig",
			injectedPath: "injected",
		},
		{
			name: "delegates to client manager get client",
			manager: testkits.NewClientManager().
				WithGetClient(func(ctx context.Context, key client.ObjectKey) (client.Client, error) {
					return fake.NewClientBuilder().Build(), nil
				}),
		},
		{
			name: "returns error when client manager get client fails",
			manager: testkits.NewClientManager().
				WithGetClient(func(ctx context.Context, key client.ObjectKey) (client.Client, error) {
					return nil, assert.AnError
				}),
			wantErr: true,
			errMsg:  assert.AnError.Error(),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			getter := &clientGetter{
				clusterName:   "test-cluster",
				clientManager: tt.manager,
			}

			if tt.injectedPath != "" {
				kubeconfigPath := writeTempKubeconfig(t)
				getter.injectedKubeconfig = kubeconfigPath
			}

			cli, err := getter.GetClient(context.Background())
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				return
			}

			assert.NoError(t, err)
			assert.NotNil(t, cli)
		})
	}
}

func TestClientGetter_GetHelmConfig(t *testing.T) {
	tests := []struct {
		name         string
		injectedPath string
		manager      *testkits.ClientManager
		wantErr      bool
		errMsg       string
	}{
		{
			name:         "returns helm config from injected kubeconfig",
			injectedPath: "injected",
		},
		{
			name: "creates helm action configuration",
			manager: testkits.NewClientManager().
				WithGetConfig(func(ctx context.Context, key client.ObjectKey) (*rest.Config, error) {
					return &rest.Config{Host: "https://test.example.com"}, nil
				}),
		},
		{
			name: "returns error when get config fails",
			manager: testkits.NewClientManager().
				WithGetConfig(func(ctx context.Context, key client.ObjectKey) (*rest.Config, error) {
					return nil, assert.AnError
				}),
			wantErr: true,
			errMsg:  "failed to get remote kubeconfig",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			getter := &clientGetter{
				clusterName:   "test-cluster",
				clientManager: tt.manager,
			}

			if tt.injectedPath != "" {
				kubeconfigPath := writeTempKubeconfig(t)
				getter.injectedKubeconfig = kubeconfigPath
			}

			helmCfg, err := getter.GetHelmConfig(context.Background())
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				return
			}

			assert.NoError(t, err)
			assert.NotNil(t, helmCfg)
		})
	}
}

func writeTempKubeconfig(t *testing.T) string {
	dir := t.TempDir()
	path := filepath.Join(dir, "kubeconfig")
	content := []byte(`apiVersion: v1
kind: Config
clusters:
- name: test
  cluster:
    server: https://127.0.0.1:6443
    insecure-skip-tls-verify: true
users:
- name: test
  user:
    token: dummy
contexts:
- name: test
  context:
    cluster: test
    user: test
current-context: test
`)

	assert.NoError(t, os.WriteFile(path, content, 0o644))
	return path
}
