package nextgentasks

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/metering/pkg/common/config"
	"github.com/tidbcloud/metering/pkg/nextgenrepoter"
	"github.com/tidbcloud/metering/test/mockcheckpoint"
	"go.uber.org/mock/gomock"
	"go.uber.org/zap"
	"go.uber.org/zap/zaptest"
)

type fakeNextGenReporter struct {
	calls int
	ctx   context.Context
	err   error
}

func (f *fakeNextGenReporter) Report(ctx context.Context) error {
	f.calls++
	f.ctx = ctx
	if f.err != nil {
		return f.err
	}
	return nil
}

func overrideReporterFactory(t *testing.T, reporter nextgenrepoter.NextGenRepoter, err error) func() {
	t.Helper()
	original := newNextGenReporter
	newNextGenReporter = func(*config.ServiceConfig, *zap.Logger) (nextgenrepoter.NextGenRepoter, error) {
		if err != nil {
			return nil, err
		}
		return reporter, nil
	}
	return func() {
		newNextGenReporter = original
	}
}

func setupMockCheckpointFactory(t *testing.T, checkpointValue int64) (*gomock.Controller, func()) {
	t.Helper()
	ctrl := gomock.NewController(t)
	mockCheckpoint := mockcheckpoint.NewMockNextGenCheckpoint(ctrl)
	mockCheckpoint.EXPECT().GetCheckpoint(gomock.Any(), "nextgen_reporter").Return(checkpointValue, nil)
	factory := &fakeCheckpointFactory{manager: mockCheckpoint}
	restore := overrideCheckpointFactory(t, factory)
	return ctrl, func() {
		restore()
		ctrl.Finish()
	}
}

func TestNewNextGenRepoterTask_MissingConfig(t *testing.T) {
	task, err := NewNextGenRepoterTask(&config.ServiceConfig{}, zaptest.NewLogger(t))
	assert.Error(t, err)
	assert.Nil(t, task)
	assert.Contains(t, err.Error(), "NextGenMeteringConfig is required")
}

func TestNewNextGenRepoterTask_ReporterDisabled(t *testing.T) {
	conf := &config.ServiceConfig{
		NextGenMeteringConfig: &config.NextGenMeteringConfig{EnableReporter: false},
	}

	task, err := NewNextGenRepoterTask(conf, zaptest.NewLogger(t))
	assert.Error(t, err)
	assert.Nil(t, task)
	assert.Contains(t, err.Error(), "NextGen reporter is disabled")
}

func TestNewNextGenRepoterTask_FactoryError(t *testing.T) {
	factoryErr := errors.New("failed to build reporter")
	restore := overrideReporterFactory(t, nil, factoryErr)
	defer restore()

	conf := &config.ServiceConfig{
		NextGenMeteringConfig: &config.NextGenMeteringConfig{EnableReporter: true},
	}

	task, err := NewNextGenRepoterTask(conf, zaptest.NewLogger(t))
	require.Error(t, err)
	assert.Nil(t, task)
	assert.Contains(t, err.Error(), "failed to create NextGen billing reporter")
	assert.ErrorIs(t, err, factoryErr)
}

func TestNewNextGenRepoterTask_Success(t *testing.T) {
	fakeReporter := &fakeNextGenReporter{}
	restoreReporter := overrideReporterFactory(t, fakeReporter, nil)
	defer restoreReporter()

	_, restoreCheckpoint := setupMockCheckpointFactory(t, 1000)
	defer restoreCheckpoint()

	conf := &config.ServiceConfig{
		NextGenMeteringConfig: &config.NextGenMeteringConfig{EnableReporter: true},
	}

	task, err := NewNextGenRepoterTask(conf, zaptest.NewLogger(t))
	require.NoError(t, err)
	assert.NotNil(t, task)
	assert.Equal(t, conf, task.conf)
	assert.Equal(t, fakeReporter, task.reporter)
}

func TestNextGenRepoterTask_Run_Success(t *testing.T) {
	fakeReporter := &fakeNextGenReporter{}
	restoreReporter := overrideReporterFactory(t, fakeReporter, nil)
	defer restoreReporter()

	ctrl := gomock.NewController(t)
	mockCheckpoint := mockcheckpoint.NewMockNextGenCheckpoint(ctrl)
	// Initial load in constructor
	mockCheckpoint.EXPECT().GetCheckpoint(gomock.Any(), "nextgen_reporter").Return(int64(1000), nil)
	// Load after successful report
	mockCheckpoint.EXPECT().GetCheckpoint(gomock.Any(), "nextgen_reporter").Return(int64(2000), nil)
	factory := &fakeCheckpointFactory{manager: mockCheckpoint}
	restoreCheckpoint := overrideCheckpointFactory(t, factory)
	defer func() {
		restoreCheckpoint()
		ctrl.Finish()
	}()

	conf := &config.ServiceConfig{
		NextGenMeteringConfig: &config.NextGenMeteringConfig{EnableReporter: true},
	}

	task, err := NewNextGenRepoterTask(conf, zaptest.NewLogger(t))
	require.NoError(t, err)

	resetGlobalDeltaLakeLock()
	InitGlobalDeltaLakeLock(zaptest.NewLogger(t))

	ctx := context.Background()
	err = task.Run(ctx)
	require.NoError(t, err)
	assert.Equal(t, 1, fakeReporter.calls)
	assert.Equal(t, ctx, fakeReporter.ctx)
}

func TestNextGenRepoterTask_Run_ReportError(t *testing.T) {
	reportErr := errors.New("report failed")
	fakeReporter := &fakeNextGenReporter{err: reportErr}
	restoreReporter := overrideReporterFactory(t, fakeReporter, nil)
	defer restoreReporter()

	_, restoreCheckpoint := setupMockCheckpointFactory(t, 1000)
	defer restoreCheckpoint()

	conf := &config.ServiceConfig{
		NextGenMeteringConfig: &config.NextGenMeteringConfig{EnableReporter: true},
	}

	task, err := NewNextGenRepoterTask(conf, zaptest.NewLogger(t))
	require.NoError(t, err)

	resetGlobalDeltaLakeLock()
	InitGlobalDeltaLakeLock(zaptest.NewLogger(t))

	err = task.Run(context.Background())
	require.Error(t, err)
	assert.EqualError(t, err, reportErr.Error())
	assert.Equal(t, 1, fakeReporter.calls)
}

func TestNextGenRepoterTask_GetTaskName(t *testing.T) {
	task := &NextGenRepoterTask{}
	assert.Equal(t, "nextgen_reporter", task.GetTaskName())
}
