package service

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"math/big"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/cluster-agent/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	"github.com/tidbcloud/cluster-service-ng/internal/cluster-agent/internal/sqlexec"
)

func TestAgentService_DeleteCloudAdmin(t *testing.T) {
	tests := []struct {
		name    string
		handler http.HandlerFunc
		deps    *v2HTTPDeps
		depsErr error
		req     *apiv1.DeleteCloudAdminReq
		wantErr bool
		wantMsg string
	}{
		{
			name: "V2 success (204)",
			handler: func(w http.ResponseWriter, r *http.Request) {
				assert.Equal(t, http.MethodDelete, r.Method)
				assert.Equal(t, "/internal/v1/users/cloud_admin", r.URL.Path)
				w.WriteHeader(http.StatusNoContent)
			},
			req:     &apiv1.DeleteCloudAdminReq{ClusterId: "cluster-1"},
			wantMsg: "success",
		},
		{
			name: "V2 returns 500 - no fallback",
			handler: func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(http.StatusInternalServerError)
				_, _ = w.Write([]byte("internal error"))
			},
			req:     &apiv1.DeleteCloudAdminReq{ClusterId: "cluster-1"},
			wantErr: true,
		},
		{
			name: "V2 returns 403 - no fallback",
			handler: func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(http.StatusForbidden)
				_, _ = w.Write([]byte("forbidden"))
			},
			req:     &apiv1.DeleteCloudAdminReq{ClusterId: "cluster-1"},
			wantErr: true,
		},
		{
			name:    "address resolution failure",
			depsErr: fmt.Errorf("failed to resolve TiDB address: no tidb group found"),
			req:     &apiv1.DeleteCloudAdminReq{ClusterId: "cluster-1"},
			wantErr: true,
		},
		{
			name: "invalid V2 base URL",
			deps: &v2HTTPDeps{
				client:  &http.Client{},
				baseURL: "http://db-group-tidb.ns.svc:10080",
			},
			req:     &apiv1.DeleteCloudAdminReq{ClusterId: "cluster-1"},
			wantErr: true,
		},
		{
			name:    "nil request",
			req:     nil,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			depsResolver := func(_ context.Context, _ client.Client, _ string) (*v2HTTPDeps, error) {
				return nil, fmt.Errorf("resolver should not be called")
			}
			switch {
			case tt.deps != nil:
				depsResolver = func(_ context.Context, _ client.Client, _ string) (*v2HTTPDeps, error) {
					return tt.deps, nil
				}
			case tt.depsErr != nil:
				depsResolver = func(_ context.Context, _ client.Client, _ string) (*v2HTTPDeps, error) {
					return nil, tt.depsErr
				}
			case tt.handler != nil:
				ts := httptest.NewTLSServer(tt.handler)
				t.Cleanup(ts.Close)

				depsResolver = func(_ context.Context, _ client.Client, _ string) (*v2HTTPDeps, error) {
					return &v2HTTPDeps{
						client:  ts.Client(),
						baseURL: ts.URL,
					}, nil
				}
			}

			svc := &AgentService{
				resolveV2HTTPDeps: depsResolver,
			}

			resp, err := svc.DeleteCloudAdmin(context.Background(), tt.req)
			if tt.wantErr {
				require.Error(t, err)
				return
			}

			require.NoError(t, err)
			require.NotNil(t, resp)
			assert.Equal(t, tt.wantMsg, resp.Message)
		})
	}
}

func TestAgentService_DeleteCloudAdmin_404FallbackToV1(t *testing.T) {
	// V2 returns 404 -> fallback to V1 privileged TiDB
	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, http.MethodDelete, r.Method)
		assert.Equal(t, "/internal/v1/users/cloud_admin", r.URL.Path)
		w.WriteHeader(http.StatusNotFound)
	}))
	t.Cleanup(ts.Close)

	stubExec := &stubPrivilegedExecutor{}
	var capturedLifecycle *privilegedTiDBLifecycle

	oldFactory := newPrivilegedTiDBExecutor
	newPrivilegedTiDBExecutor = func(lifecycle sqlexec.PrivilegedLifecycle) sqlexec.Executor {
		lc, ok := lifecycle.(*privilegedTiDBLifecycle)
		require.True(t, ok)
		capturedLifecycle = lc
		return stubExec
	}
	t.Cleanup(func() {
		newPrivilegedTiDBExecutor = oldFactory
	})

	svc := &AgentService{
		resolveV2HTTPDeps: func(_ context.Context, _ client.Client, _ string) (*v2HTTPDeps, error) {
			return &v2HTTPDeps{
				client:  ts.Client(),
				baseURL: ts.URL,
			}, nil
		},
	}

	resp, err := svc.DeleteCloudAdmin(context.Background(), &apiv1.DeleteCloudAdminReq{
		ClusterId: "cluster-1",
	})
	require.NoError(t, err)
	require.NotNil(t, resp)
	assert.Equal(t, "success", resp.Message)

	require.Len(t, stubExec.calls, 1)
	assert.Equal(t, "cluster-1", stubExec.calls[0].clusterID)
	assert.Equal(t, "DeleteCloudAdmin", stubExec.calls[0].cmdName)

	require.NotNil(t, capturedLifecycle)
	assert.Equal(t, "maintenance_admin", capturedLifecycle.username)
	assert.True(t, capturedLifecycle.enableAuditlog)
}

type privilegedExecutorCall struct {
	clusterID string
	cmdName   string
}

type stubPrivilegedExecutor struct {
	calls []privilegedExecutorCall
	err   error
}

func (e *stubPrivilegedExecutor) Execute(
	_ context.Context,
	clusterID string,
	cmd sqlexec.Command,
) (sqlexec.Result, error) {
	e.calls = append(e.calls, privilegedExecutorCall{
		clusterID: clusterID,
		cmdName:   cmd.Name(),
	})
	return sqlexec.Result{}, e.err
}

func (e *stubPrivilegedExecutor) Query(
	_ context.Context,
	_ string,
	_ sqlexec.QueryCommand,
) (sqlexec.QueryResult, error) {
	return sqlexec.QueryResult{}, fmt.Errorf("Query should not be called")
}

func TestDefaultResolveV2HTTPDeps_SetsTLSServerNameForTiDBWildcardCert(t *testing.T) {
	namespace := "tidb123"
	clusterID := "cluster-1"
	groupName := "group-a"

	t.Setenv("NAMESPACE", namespace)

	certPEM, keyPEM := generateTLSPEMForTest(t)
	secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      tlsClientSecret,
			Namespace: namespace,
		},
		Data: map[string][]byte{
			corev1.ServiceAccountRootCAKey: certPEM,
			corev1.TLSCertKey:              certPEM,
			corev1.TLSPrivateKeyKey:        keyPEM,
		},
	}
	pod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "tidb-pod-0",
			Namespace: namespace,
			Labels: map[string]string{
				"tags.tidbcloud.com/component":  "tidb",
				"tags.tidbcloud.com/cluster":    clusterID,
				"tags.tidbcloud.com/tidb-group": groupName,
			},
		},
	}

	scheme := runtime.NewScheme()
	require.NoError(t, corev1.AddToScheme(scheme))

	cli := fake.NewClientBuilder().
		WithScheme(scheme).
		WithObjects(secret, pod).
		Build()

	deps, err := defaultResolveV2HTTPDeps(context.Background(), cli, clusterID)
	require.NoError(t, err)
	require.NotNil(t, deps)

	assert.Equal(t, "https://db-group-a-tidb.tidb123.svc:10080", deps.baseURL)
	transport, ok := deps.client.Transport.(*http.Transport)
	require.True(t, ok)
	require.NotNil(t, transport.TLSClientConfig)
	assert.Equal(t, "tidb.db-cluster.tidb123.svc", transport.TLSClientConfig.ServerName)
}

func generateTLSPEMForTest(t *testing.T) ([]byte, []byte) {
	t.Helper()

	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	require.NoError(t, err)

	serialNumberLimit := big.NewInt(1 << 62)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	require.NoError(t, err)

	now := time.Now()
	template := &x509.Certificate{
		SerialNumber:          serialNumber,
		Subject:               pkix.Name{CommonName: "test-client"},
		NotBefore:             now.Add(-time.Hour),
		NotAfter:              now.Add(time.Hour),
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
		BasicConstraintsValid: true,
		IsCA:                  true,
	}

	derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
	require.NoError(t, err)

	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)})
	return certPEM, keyPEM
}
