package billing_svc

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/require"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
	"github.com/tidbcloud/serverless-service/internal/domain/facade/mock_facade"
	"github.com/tidbcloud/serverless-service/internal/domain/repo/mock_repo"
	"go.uber.org/mock/gomock"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

func TestCompleteExportMetering(t *testing.T) {
	const (
		proxyEndpoint = "proxy.example.com"
		exportID      = "exp-1"
		projectID     = uint64(42)
		usage         = int64(123)
	)

	t.Run("annotates before deleting and marks metering successful last", func(t *testing.T) {
		ctrl := gomock.NewController(t)
		infraFacade := mock_facade.NewMockInfraFacade(ctrl)
		exportRepo := mock_repo.NewMockExportRepo(ctrl)
		svc := &MeteringService{infraFacade: infraFacade, exportRepo: exportRepo}
		export := &entity.Export{ID: exportID, ProjectID: "42", MeteringStatus: constant.ExportMeteringStateRunning}
		exportCR := &cloudv1.Export{ObjectMeta: metav1.ObjectMeta{Name: exportID}}

		gomock.InOrder(
			infraFacade.EXPECT().GetExport(gomock.Any(), proxyEndpoint, projectID, exportID).Return(exportCR, nil),
			infraFacade.EXPECT().UpdateExport(gomock.Any(), proxyEndpoint, exportCR).DoAndReturn(
				func(_ context.Context, _ string, updated *cloudv1.Export) error {
					require.Equal(t, "123", updated.Annotations[constant.ExportMeteringAnnotation])
					return nil
				},
			),
			infraFacade.EXPECT().DeleteExport(gomock.Any(), proxyEndpoint, projectID, exportID).Return(nil),
			exportRepo.EXPECT().UpdateMeteringStatus(gomock.Any(), export).DoAndReturn(
				func(_ context.Context, updated *entity.Export) error {
					require.Equal(t, constant.ExportMeteringStateSucceed, updated.MeteringStatus)
					return nil
				},
			),
		)

		require.NoError(t, svc.completeExportMetering(context.Background(), export, usage, proxyEndpoint))
	})

	t.Run("does not delete when annotation update fails", func(t *testing.T) {
		ctrl := gomock.NewController(t)
		infraFacade := mock_facade.NewMockInfraFacade(ctrl)
		exportRepo := mock_repo.NewMockExportRepo(ctrl)
		svc := &MeteringService{infraFacade: infraFacade, exportRepo: exportRepo}
		export := &entity.Export{ID: exportID, ProjectID: "42", MeteringStatus: constant.ExportMeteringStateRunning}
		exportCR := &cloudv1.Export{ObjectMeta: metav1.ObjectMeta{Name: exportID}}
		updateErr := errors.New("update failed")

		gomock.InOrder(
			infraFacade.EXPECT().GetExport(gomock.Any(), proxyEndpoint, projectID, exportID).Return(exportCR, nil),
			infraFacade.EXPECT().UpdateExport(gomock.Any(), proxyEndpoint, exportCR).Return(updateErr),
		)

		err := svc.completeExportMetering(context.Background(), export, usage, proxyEndpoint)
		require.ErrorIs(t, err, updateErr)
		require.Equal(t, constant.ExportMeteringStateRunning, export.MeteringStatus)
	})

	t.Run("does not mark metering successful when deletion fails", func(t *testing.T) {
		ctrl := gomock.NewController(t)
		infraFacade := mock_facade.NewMockInfraFacade(ctrl)
		exportRepo := mock_repo.NewMockExportRepo(ctrl)
		svc := &MeteringService{infraFacade: infraFacade, exportRepo: exportRepo}
		export := &entity.Export{ID: exportID, ProjectID: "42", MeteringStatus: constant.ExportMeteringStateRunning}
		exportCR := &cloudv1.Export{ObjectMeta: metav1.ObjectMeta{Name: exportID}}
		deleteErr := errors.New("delete failed")

		gomock.InOrder(
			infraFacade.EXPECT().GetExport(gomock.Any(), proxyEndpoint, projectID, exportID).Return(exportCR, nil),
			infraFacade.EXPECT().UpdateExport(gomock.Any(), proxyEndpoint, exportCR).Return(nil),
			infraFacade.EXPECT().DeleteExport(gomock.Any(), proxyEndpoint, projectID, exportID).Return(deleteErr),
		)

		err := svc.completeExportMetering(context.Background(), export, usage, proxyEndpoint)
		require.ErrorIs(t, err, deleteErr)
		require.Equal(t, constant.ExportMeteringStateRunning, export.MeteringStatus)
	})

	t.Run("completes database metering when export CR is already absent", func(t *testing.T) {
		ctrl := gomock.NewController(t)
		infraFacade := mock_facade.NewMockInfraFacade(ctrl)
		exportRepo := mock_repo.NewMockExportRepo(ctrl)
		svc := &MeteringService{infraFacade: infraFacade, exportRepo: exportRepo}
		export := &entity.Export{ID: exportID, ProjectID: "42", MeteringStatus: constant.ExportMeteringStateRunning}
		notFound := apierrors.NewNotFound(
			schema.GroupResource{Group: "cloud.pingcap.com", Resource: "exports"},
			exportID,
		)

		gomock.InOrder(
			infraFacade.EXPECT().GetExport(gomock.Any(), proxyEndpoint, projectID, exportID).Return(nil, notFound),
			exportRepo.EXPECT().UpdateMeteringStatus(gomock.Any(), export).Return(nil),
		)

		require.NoError(t, svc.completeExportMetering(context.Background(), export, usage, proxyEndpoint))
		require.Equal(t, constant.ExportMeteringStateSucceed, export.MeteringStatus)
	})
}
