package opsservice

import (
	"context"
	"testing"

	helmv2 "github.com/fluxcd/helm-controller/api/v2"
	"github.com/fluxcd/pkg/apis/meta"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/errors"
	"github.com/tidbcloud/observability/pkg/common/message"
)

type fakeHelmReleaseReaderWriter struct {
	getHR     *helmv2.HelmRelease
	getByName map[string]*helmv2.HelmRelease
	created   *helmv2.HelmRelease
	updated   []*helmv2.HelmRelease
	createErr error
	updateErr error
	getErr    error
	deleteErr error
}

func (f *fakeHelmReleaseReaderWriter) Create(_ context.Context, hr *helmv2.HelmRelease) error {
	f.created = hr.DeepCopy()
	return f.createErr
}

func (f *fakeHelmReleaseReaderWriter) Get(_ context.Context, _, name string) (*helmv2.HelmRelease, error) {
	if f.getErr != nil {
		return nil, f.getErr
	}
	if hr, ok := f.getByName[name]; ok {
		return hr.DeepCopy(), nil
	}
	if f.getHR != nil {
		return f.getHR.DeepCopy(), nil
	}
	return &helmv2.HelmRelease{}, nil
}

func (f *fakeHelmReleaseReaderWriter) List(context.Context, string, message.ContinueRequest) (*helmv2.HelmReleaseList, error) {
	return &helmv2.HelmReleaseList{}, nil
}

func (f *fakeHelmReleaseReaderWriter) ListAll(context.Context, message.ContinueRequest) (*helmv2.HelmReleaseList, error) {
	return &helmv2.HelmReleaseList{}, nil
}

func (f *fakeHelmReleaseReaderWriter) Update(_ context.Context, hr *helmv2.HelmRelease) error {
	f.updated = append(f.updated, hr.DeepCopy())
	return f.updateErr
}

func (f *fakeHelmReleaseReaderWriter) Delete(context.Context, string, string) error {
	return f.deleteErr
}

func (f *fakeHelmReleaseReaderWriter) IsHelmReleaseExists(context.Context, string, string) (bool, error) {
	return false, nil
}

func assertReconcileAnnotations(t *testing.T, hr *helmv2.HelmRelease) {
	t.Helper()

	requestToken := hr.Annotations[meta.ReconcileRequestAnnotation]
	require.NotEmpty(t, requestToken)
	assert.Equal(t, requestToken, hr.Annotations[helmv2.ResetRequestAnnotation])
}

func TestCreateHelmRelease_StampsReconcileAnnotations(t *testing.T) {
	rw := &fakeHelmReleaseReaderWriter{}
	svc := &HelmReleaseService{helmReleaseRW: rw}

	_, err := svc.CreateHelmRelease(context.Background(), message.CreateHelmReleaseReq{
		Name:      "regional-server",
		Namespace: "observability",
		Component: "regional-server",
		Config: message.HelmReleaseConfig{
			ExpectedDigest: "sha256:abc",
		},
	})

	require.NoError(t, err)
	require.NotNil(t, rw.created)
	assertReconcileAnnotations(t, rw.created)
	assert.Equal(t, "sha256:abc", rw.created.Annotations[message.HelmReleaseExpectedDigestAnnotation])
}

func TestUpdateHelmRelease_StampsReconcileAnnotations(t *testing.T) {
	rw := &fakeHelmReleaseReaderWriter{
		getHR: &helmv2.HelmRelease{
			ObjectMeta: metav1.ObjectMeta{
				Name:            "regional-server",
				Namespace:       constants.OpsSystemNamespace,
				ResourceVersion: "7",
			},
			Spec: helmv2.HelmReleaseSpec{
				TargetNamespace: "observability",
			},
		},
	}
	svc := &HelmReleaseService{helmReleaseRW: rw}

	_, err := svc.UpdateHelmRelease(context.Background(), message.UpdateHelmReleaseReq{
		Name:      "regional-server",
		Component: "regional-server",
		Config:    message.HelmReleaseConfig{},
	})

	require.NoError(t, err)
	require.Len(t, rw.updated, 1)
	assert.Equal(t, "7", rw.updated[0].ResourceVersion)
	assertReconcileAnnotations(t, rw.updated[0])
}

func TestUpdateBasicAuth_StampsReconcileAnnotations(t *testing.T) {
	alertmanagerValues, err := message.ConvertValuesToJSON(map[string]interface{}{
		"extraSecret": map[string]interface{}{
			"data": map[string]interface{}{
				"username": "old-user",
				"password": "old-pass",
			},
		},
	})
	require.NoError(t, err)

	kubePromValues, err := message.ConvertValuesToJSON(map[string]interface{}{
		"alertmanager": map[string]interface{}{
			"config": map[string]interface{}{
				"receivers": []interface{}{
					map[string]interface{}{
						"name": "pingcap-primary",
						"webhook_configs": []interface{}{
							map[string]interface{}{
								"http_config": map[string]interface{}{
									"basic_auth": map[string]interface{}{
										"username": "old-user",
										"password": "old-pass",
									},
								},
							},
						},
					},
				},
			},
		},
	})
	require.NoError(t, err)

	rw := &fakeHelmReleaseReaderWriter{
		getByName: map[string]*helmv2.HelmRelease{
			constants.ReleaseNameAlertManager: &helmv2.HelmRelease{
				ObjectMeta: metav1.ObjectMeta{
					Name:            constants.ReleaseNameAlertManager,
					Namespace:       constants.OpsSystemNamespace,
					ResourceVersion: "11",
				},
				Spec: helmv2.HelmReleaseSpec{
					Values: alertmanagerValues,
				},
			},
			constants.ReleaseNameKubePrometheusStack: &helmv2.HelmRelease{
				ObjectMeta: metav1.ObjectMeta{
					Name:            constants.ReleaseNameKubePrometheusStack,
					Namespace:       constants.OpsSystemNamespace,
					ResourceVersion: "12",
				},
				Spec: helmv2.HelmReleaseSpec{
					Values: kubePromValues,
				},
			},
		},
	}
	svc := &HelmReleaseService{helmReleaseRW: rw}

	_, err = svc.UpdateBasicAuth(context.Background(), message.UpdateBasicAuthReq{
		Username: "new-user",
		Password: "new-pass",
	})

	require.NoError(t, err)
	require.Len(t, rw.updated, 2)
	for _, hr := range rw.updated {
		assertReconcileAnnotations(t, hr)
	}
}

func TestGetHelmRelease_PreservesNotFoundError(t *testing.T) {
	notFoundErr := errors.NewErrorf(errors.HelmReleaseNotFound, "HelmRelease ops-system/regional-server is not found")
	svc := &HelmReleaseService{helmReleaseRW: &fakeHelmReleaseReaderWriter{getErr: notFoundErr}}

	_, err := svc.GetHelmRelease(context.Background(), message.GetHelmReleaseReq{Name: "regional-server"})

	assert.Error(t, err)
	assert.True(t, errors.Is(err, errors.HelmReleaseNotFound))
}

func TestDeleteHelmRelease_PreservesNotFoundError(t *testing.T) {
	notFoundErr := errors.NewErrorf(errors.HelmReleaseNotFound, "HelmRelease ops-system/regional-server is not found")
	svc := &HelmReleaseService{helmReleaseRW: &fakeHelmReleaseReaderWriter{deleteErr: notFoundErr}}

	_, err := svc.DeleteHelmRelease(context.Background(), message.DeleteHelmReleaseReq{Name: "regional-server"})

	assert.Error(t, err)
	assert.True(t, errors.Is(err, errors.HelmReleaseNotFound))
}
