// Copyright 2025 The etcd Authors
//
// 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 etcdutl

import (
	"testing"

	"github.com/stretchr/testify/require"
	"go.uber.org/zap"

	"go.etcd.io/etcd/api/v3/etcdserverpb"
	"go.etcd.io/etcd/client/pkg/v3/fileutil"
	"go.etcd.io/etcd/pkg/v3/pbutil"
	"go.etcd.io/etcd/server/v3/etcdserver/api/snap"
	"go.etcd.io/etcd/server/v3/storage/datadir"
	"go.etcd.io/etcd/server/v3/storage/wal"
	"go.etcd.io/etcd/server/v3/storage/wal/walpb"
	"go.etcd.io/raft/v3/raftpb"
)

func TestGetLatestWalSnap(t *testing.T) {
	testCases := []struct {
		name                  string
		walSnaps              []*walpb.Snapshot
		snapshots             []*raftpb.Snapshot
		expectedLatestWALSnap *walpb.Snapshot
	}{
		{
			name: "wal snapshot records match the snapshot files",
			walSnaps: []*walpb.Snapshot{
				{Index: new(uint64(10)), Term: new(uint64(2))},
				{Index: new(uint64(20)), Term: new(uint64(3))},
				{Index: new(uint64(30)), Term: new(uint64(5))},
			},
			snapshots: []*raftpb.Snapshot{
				{Metadata: &raftpb.SnapshotMetadata{Index: new(uint64(10)), Term: new(uint64(2))}},
				{Metadata: &raftpb.SnapshotMetadata{Index: new(uint64(20)), Term: new(uint64(3))}},
				{Metadata: &raftpb.SnapshotMetadata{Index: new(uint64(30)), Term: new(uint64(5))}},
			},
			expectedLatestWALSnap: &walpb.Snapshot{Index: new(uint64(30)), Term: new(uint64(5))},
		},
		{
			name: "there are orphan snapshot files",
			walSnaps: []*walpb.Snapshot{
				{Index: new(uint64(10)), Term: new(uint64(2))},
				{Index: new(uint64(20)), Term: new(uint64(3))},
				{Index: new(uint64(35)), Term: new(uint64(5))},
			},
			snapshots: []*raftpb.Snapshot{
				{Metadata: &raftpb.SnapshotMetadata{Index: new(uint64(10)), Term: new(uint64(2))}},
				{Metadata: &raftpb.SnapshotMetadata{Index: new(uint64(20)), Term: new(uint64(3))}},
				{Metadata: &raftpb.SnapshotMetadata{Index: new(uint64(35)), Term: new(uint64(5))}},
				{Metadata: &raftpb.SnapshotMetadata{Index: new(uint64(40)), Term: new(uint64(6))}},
				{Metadata: &raftpb.SnapshotMetadata{Index: new(uint64(50)), Term: new(uint64(7))}},
			},
			expectedLatestWALSnap: &walpb.Snapshot{Index: new(uint64(35)), Term: new(uint64(5))},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			dataDir := t.TempDir()
			lg := zap.NewNop()

			require.NoError(t, fileutil.TouchDirAll(lg, datadir.ToMemberDir(dataDir)))
			require.NoError(t, fileutil.TouchDirAll(lg, datadir.ToWALDir(dataDir)))
			require.NoError(t, fileutil.TouchDirAll(lg, datadir.ToSnapDir(dataDir)))

			// populate wal file
			w, err := wal.Create(lg, datadir.ToWALDir(dataDir), pbutil.MustMarshalMessage(
				&etcdserverpb.Metadata{
					NodeID:    new(uint64(1)),
					ClusterID: new(uint64(2)),
				},
			))
			require.NoError(t, err)

			for _, walSnap := range tc.walSnaps {
				walSnap.ConfState = &raftpb.ConfState{Voters: []uint64{1}}
				walErr := w.SaveSnapshot(walSnap)
				require.NoError(t, walErr)
				walErr = w.Save(&raftpb.HardState{Term: new(walSnap.GetTerm()), Commit: new(walSnap.GetIndex()), Vote: new(uint64(1))}, nil)
				require.NoError(t, walErr)
			}
			err = w.Close()
			require.NoError(t, err)

			// generate snapshot files
			ss := snap.New(lg, datadir.ToSnapDir(dataDir))
			for _, snap := range tc.snapshots {
				snap.Metadata.ConfState = &raftpb.ConfState{Voters: []uint64{1}}
				snapErr := ss.SaveSnap(snap)
				require.NoError(t, snapErr)
			}

			walSnap, err := getLatestWALSnap(lg, dataDir)
			require.NoError(t, err)

			require.Equal(t, tc.expectedLatestWALSnap.GetTerm(), walSnap.GetTerm())
			require.Equal(t, tc.expectedLatestWALSnap.GetIndex(), walSnap.GetIndex())
		})
	}
}
