package process

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/prometheus/client_golang/prometheus/testutil"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/observability/cmd/diagnosis-compute/pkg/config"
	"github.com/tidbcloud/observability/cmd/diagnosis-compute/pkg/metrics"
	"github.com/tidbcloud/observability/cmd/diagnosis-compute/pkg/types"
	"golang.org/x/sync/semaphore"
)

func TestShouldDeleteConprofProfile(t *testing.T) {
	base := time.Date(2026, 2, 4, 0, 0, 0, 0, time.UTC).Unix()

	assert.False(t, shouldDeleteConprofProfile(base, 2))
	assert.True(t, shouldDeleteConprofProfile(base+60, 2))
	assert.False(t, shouldDeleteConprofProfile(base+120, 2))

	assert.False(t, shouldDeleteConprofProfile(base, 4))
	assert.True(t, shouldDeleteConprofProfile(base+60, 4))
	assert.True(t, shouldDeleteConprofProfile(base+120, 4))
	assert.True(t, shouldDeleteConprofProfile(base+180, 4))
	assert.False(t, shouldDeleteConprofProfile(base+240, 4))
}

func TestCleanupConprofHourFirstHalving(t *testing.T) {
	storeDir := t.TempDir()
	config.S3sem = semaphore.NewWeighted(10)
	store := NewMockStorage2(storeDir)
	ctx := context.Background()

	info := types.ClusterInfo{
		Env:         "prod",
		TenantID:    "tenant-cleanup",
		ProjectID:   "project-cleanup",
		ClusterID:   "cluster-cleanup",
		StorageID:   "cluster-cleanup",
		ClusterType: types.ClusterTypeDedicated,
	}

	hourBegin := time.Date(2026, 2, 4, 0, 0, 0, 0, time.UTC).Unix()
	profilesPrefix := getProfilesPrefix(info)
	bucket := config.BucketName()

	keepKey := fmt.Sprintf("%s%d-pd-cpu-keep.log.gz", profilesPrefix, hourBegin)
	deleteKey := fmt.Sprintf("%s%d-pd-cpu-delete.log.gz", profilesPrefix, hourBegin+60)
	require.NoError(t, store.SaveObject(ctx, bucket, keepKey, strings.NewReader("keep")))
	require.NoError(t, store.SaveObject(ctx, bucket, deleteKey, strings.NewReader("delete-me")))

	metrics.ConprofCleanupFiles.Reset()
	metrics.ConprofCleanupBytes.Reset()

	cleanupConprofHour(ctx, store, bucket, info, hourBegin, 2, "first_halving")

	_, err := store.Object(ctx, bucket, keepKey)
	require.NoError(t, err)
	_, err = store.Object(ctx, bucket, deleteKey)
	require.Error(t, err)

	assert.InDelta(t, 1.0,
		testutil.ToFloat64(metrics.ConprofCleanupFiles.WithLabelValues("prod", "tenant-cleanup", "cluster-cleanup", "first_halving", "deleted")),
		0.001,
	)
}

func TestRunConprofRetentionCleanupTargets(t *testing.T) {
	storeDir := t.TempDir()
	config.S3sem = semaphore.NewWeighted(10)
	store := NewMockStorage2(storeDir)
	ctx := context.Background()

	info := types.ClusterInfo{
		Env:         "prod",
		TenantID:    "tenant-target",
		ProjectID:   "project-target",
		ClusterID:   "cluster-target",
		StorageID:   "cluster-target",
		ClusterType: types.ClusterTypeDedicated,
	}

	currentHour := time.Date(2026, 2, 19, 0, 0, 0, 0, time.UTC).Unix()
	firstHour := currentHour - config.ConprofFirstHalvingAgeSeconds()
	secondHour := currentHour - config.ConprofSecondHalvingAgeSeconds()
	profilesPrefix := getProfilesPrefix(info)
	bucket := config.BucketName()

	firstDeleteKey := fmt.Sprintf("%s%d-pd-cpu-delete.log.gz", profilesPrefix, firstHour+60)
	secondDeleteKey := fmt.Sprintf("%s%d-pd-cpu-delete.log.gz", profilesPrefix, secondHour+60)
	require.NoError(t, store.SaveObject(ctx, bucket, firstDeleteKey, strings.NewReader("first")))
	require.NoError(t, store.SaveObject(ctx, bucket, secondDeleteKey, strings.NewReader("second")))

	runConprofRetentionCleanup(ctx, store, bucket, info, currentHour)

	_, err := store.Object(ctx, bucket, firstDeleteKey)
	require.Error(t, err)
	_, err = store.Object(ctx, bucket, secondDeleteKey)
	require.Error(t, err)

	_, err = os.Stat(filepath.Join(storeDir, bucket, filepath.FromSlash(firstDeleteKey)))
	assert.True(t, os.IsNotExist(err))
	_, err = os.Stat(filepath.Join(storeDir, bucket, filepath.FromSlash(secondDeleteKey)))
	assert.True(t, os.IsNotExist(err))
}

func TestRunConprofRetentionCleanupCustomDays(t *testing.T) {
	config.ConprofFirstHalvingDays = 3
	config.ConprofSecondHalvingDays = 7
	t.Cleanup(func() {
		config.ConprofFirstHalvingDays = 7
		config.ConprofSecondHalvingDays = 15
	})

	storeDir := t.TempDir()
	config.S3sem = semaphore.NewWeighted(10)
	store := NewMockStorage2(storeDir)
	ctx := context.Background()

	info := types.ClusterInfo{
		Env:         "prod",
		TenantID:    "tenant-custom",
		ProjectID:   "project-custom",
		ClusterID:   "cluster-custom",
		StorageID:   "cluster-custom",
		ClusterType: types.ClusterTypeDedicated,
	}

	currentHour := time.Date(2026, 2, 10, 0, 0, 0, 0, time.UTC).Unix()
	firstHour := currentHour - config.ConprofFirstHalvingAgeSeconds()
	secondHour := currentHour - config.ConprofSecondHalvingAgeSeconds()
	profilesPrefix := getProfilesPrefix(info)
	bucket := config.BucketName()

	firstDeleteKey := fmt.Sprintf("%s%d-pd-cpu-delete.log.gz", profilesPrefix, firstHour+60)
	secondDeleteKey := fmt.Sprintf("%s%d-pd-cpu-delete.log.gz", profilesPrefix, secondHour+60)
	require.NoError(t, store.SaveObject(ctx, bucket, firstDeleteKey, strings.NewReader("first")))
	require.NoError(t, store.SaveObject(ctx, bucket, secondDeleteKey, strings.NewReader("second")))

	runConprofRetentionCleanup(ctx, store, bucket, info, currentHour)

	_, err := store.Object(ctx, bucket, firstDeleteKey)
	require.Error(t, err)
	_, err = store.Object(ctx, bucket, secondDeleteKey)
	require.Error(t, err)
}
