// Copyright 2024 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package session

import (
	"context"
	"fmt"
	"testing"

	"github.com/pingcap/tidb/pkg/config"
	"github.com/pingcap/tidb/pkg/meta"
	"github.com/pingcap/tidb/pkg/tidbworker"
	"github.com/stretchr/testify/require"
)

// TestGCV2WorkerAbortsOnBootstrapUpgrade verifies that when TiDB runs as a GCV2 worker
// and a bootstrap upgrade is detected, AbortGCV2 is called before the upgrade proceeds.
// In production, GCV2 workers run with tidb_enable_ddl=false, which causes a fatal
// "DDL must be enabled when upgrading" without this fix.
func TestGCV2WorkerAbortsOnBootstrapUpgrade(t *testing.T) {
	store, dom := CreateStoreAndBootstrap(t)
	defer func() { require.NoError(t, store.Close()) }()

	se := CreateSessionAndSetID(t, store)

	// Downgrade the bootstrap version by 1 to trigger the upgrade path.
	txn, err := store.Begin()
	require.NoError(t, err)
	m := meta.NewMutator(txn)
	require.NoError(t, m.FinishBootstrap(currentBootstrapVersion-1))
	require.NoError(t, txn.Commit(context.Background()))
	MustExec(t, se, fmt.Sprintf(
		"update mysql.tidb set variable_value='%d' where variable_name='tidb_server_version'",
		currentBootstrapVersion-1,
	))
	MustExec(t, se, "commit")
	unsetStoreBootstrapped(store.UUID())
	dom.Close()

	mock := &gcv2MockManager{}
	tidbworker.GlobalTiDBWorkerManager = mock
	t.Cleanup(func() { tidbworker.GlobalTiDBWorkerManager = nil })

	dom, err = BootstrapSession(store)
	require.NoError(t, err)
	dom.Close()

	require.True(t, mock.abortCalled, "AbortGCV2 must be called when a GCV2 worker encounters a bootstrap upgrade")
}

// gcv2MockManager poses as a GCV2 worker. Embeds NoopManager so only the two
// meaningful methods need to be defined here.
type gcv2MockManager struct {
	tidbworker.NoopManager
	abortCalled bool
}

func (m *gcv2MockManager) Role() string { return config.RoleGCV2Worker }
func (m *gcv2MockManager) AbortGCV2(_ context.Context) error {
	m.abortCalled = true
	return nil
}
