package volume_server_grpc_test

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/seaweedfs/seaweedfs/test/volume_server/framework"
	"github.com/seaweedfs/seaweedfs/test/volume_server/matrix"
	"github.com/seaweedfs/seaweedfs/weed/pb/remote_pb"
	"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
)

func TestFetchAndWriteNeedleMaintenanceAndMissingVolume(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test in short mode")
	}

	clusterHarness := framework.StartVolumeCluster(t, matrix.P1())
	conn, grpcClient := framework.DialVolumeServer(t, clusterHarness.VolumeGRPCAddress())
	defer conn.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	_, err := grpcClient.FetchAndWriteNeedle(ctx, &volume_server_pb.FetchAndWriteNeedleRequest{
		VolumeId: 98781,
		NeedleId: 1,
	})
	if err == nil || !strings.Contains(err.Error(), "not found volume id") {
		t.Fatalf("FetchAndWriteNeedle missing-volume error mismatch: %v", err)
	}

	stateResp, err := grpcClient.GetState(ctx, &volume_server_pb.GetStateRequest{})
	if err != nil {
		t.Fatalf("GetState failed: %v", err)
	}
	_, err = grpcClient.SetState(ctx, &volume_server_pb.SetStateRequest{
		State: &volume_server_pb.VolumeServerState{
			Maintenance: true,
			Version:     stateResp.GetState().GetVersion(),
		},
	})
	if err != nil {
		t.Fatalf("SetState maintenance=true failed: %v", err)
	}

	_, err = grpcClient.FetchAndWriteNeedle(ctx, &volume_server_pb.FetchAndWriteNeedleRequest{
		VolumeId: 1,
		NeedleId: 1,
	})
	if err == nil || !strings.Contains(err.Error(), "maintenance mode") {
		t.Fatalf("FetchAndWriteNeedle maintenance error mismatch: %v", err)
	}
}

func TestFetchAndWriteNeedleInvalidRemoteConfig(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test in short mode")
	}

	clusterHarness := framework.StartVolumeCluster(t, matrix.P1())
	conn, grpcClient := framework.DialVolumeServer(t, clusterHarness.VolumeGRPCAddress())
	defer conn.Close()

	const volumeID = uint32(88)
	framework.AllocateVolume(t, grpcClient, volumeID, "")

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	_, err := grpcClient.FetchAndWriteNeedle(ctx, &volume_server_pb.FetchAndWriteNeedleRequest{
		VolumeId: volumeID,
		NeedleId: 1,
		Cookie:   1,
		Size:     1,
		RemoteConf: &remote_pb.RemoteConf{
			Name: "it-invalid-remote",
			Type: "does-not-exist",
		},
		RemoteLocation: &remote_pb.RemoteStorageLocation{
			Name: "it-invalid-remote",
			Path: "/test",
		},
	})
	if err == nil || !strings.Contains(err.Error(), "get remote client") {
		t.Fatalf("FetchAndWriteNeedle invalid-remote error mismatch: %v", err)
	}
}

func TestVolumeTierMoveDatToRemoteErrorPaths(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test in short mode")
	}

	clusterHarness := framework.StartVolumeCluster(t, matrix.P1())
	conn, grpcClient := framework.DialVolumeServer(t, clusterHarness.VolumeGRPCAddress())
	defer conn.Close()

	const volumeID = uint32(85)
	const collection = "tier-collection"
	framework.AllocateVolume(t, grpcClient, volumeID, collection)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	missingStream, err := grpcClient.VolumeTierMoveDatToRemote(ctx, &volume_server_pb.VolumeTierMoveDatToRemoteRequest{
		VolumeId:               98782,
		Collection:             collection,
		DestinationBackendName: "dummy",
	})
	if err == nil {
		_, err = missingStream.Recv()
	}
	if err == nil || !strings.Contains(err.Error(), "not found") {
		t.Fatalf("VolumeTierMoveDatToRemote missing-volume error mismatch: %v", err)
	}

	mismatchStream, err := grpcClient.VolumeTierMoveDatToRemote(ctx, &volume_server_pb.VolumeTierMoveDatToRemoteRequest{
		VolumeId:               volumeID,
		Collection:             "wrong-collection",
		DestinationBackendName: "dummy",
	})
	if err == nil {
		_, err = mismatchStream.Recv()
	}
	if err == nil || !strings.Contains(err.Error(), "unexpected input") {
		t.Fatalf("VolumeTierMoveDatToRemote collection mismatch error mismatch: %v", err)
	}

	stateResp, err := grpcClient.GetState(ctx, &volume_server_pb.GetStateRequest{})
	if err != nil {
		t.Fatalf("GetState failed: %v", err)
	}
	_, err = grpcClient.SetState(ctx, &volume_server_pb.SetStateRequest{
		State: &volume_server_pb.VolumeServerState{
			Maintenance: true,
			Version:     stateResp.GetState().GetVersion(),
		},
	})
	if err != nil {
		t.Fatalf("SetState maintenance=true failed: %v", err)
	}

	maintenanceStream, err := grpcClient.VolumeTierMoveDatToRemote(ctx, &volume_server_pb.VolumeTierMoveDatToRemoteRequest{
		VolumeId:               volumeID,
		Collection:             collection,
		DestinationBackendName: "dummy",
	})
	if err == nil {
		_, err = maintenanceStream.Recv()
	}
	if err == nil || !strings.Contains(err.Error(), "maintenance mode") {
		t.Fatalf("VolumeTierMoveDatToRemote maintenance error mismatch: %v", err)
	}
}

func TestVolumeTierMoveDatToRemoteMissingBackend(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test in short mode")
	}

	clusterHarness := framework.StartVolumeCluster(t, matrix.P1())
	conn, grpcClient := framework.DialVolumeServer(t, clusterHarness.VolumeGRPCAddress())
	defer conn.Close()

	const volumeID = uint32(89)
	const collection = "tier-missing-backend"
	framework.AllocateVolume(t, grpcClient, volumeID, collection)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	stream, err := grpcClient.VolumeTierMoveDatToRemote(ctx, &volume_server_pb.VolumeTierMoveDatToRemoteRequest{
		VolumeId:               volumeID,
		Collection:             collection,
		DestinationBackendName: "definitely-missing-backend",
	})
	if err == nil {
		_, err = stream.Recv()
	}
	if err == nil || !strings.Contains(err.Error(), "destination definitely-missing-backend not found") {
		t.Fatalf("VolumeTierMoveDatToRemote missing-backend error mismatch: %v", err)
	}
}

func TestVolumeTierMoveDatFromRemoteErrorPaths(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test in short mode")
	}

	clusterHarness := framework.StartVolumeCluster(t, matrix.P1())
	conn, grpcClient := framework.DialVolumeServer(t, clusterHarness.VolumeGRPCAddress())
	defer conn.Close()

	const volumeID = uint32(86)
	const collection = "tier-download-collection"
	framework.AllocateVolume(t, grpcClient, volumeID, collection)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	missingStream, err := grpcClient.VolumeTierMoveDatFromRemote(ctx, &volume_server_pb.VolumeTierMoveDatFromRemoteRequest{
		VolumeId:   98783,
		Collection: collection,
	})
	if err == nil {
		_, err = missingStream.Recv()
	}
	if err == nil || !strings.Contains(err.Error(), "not found") {
		t.Fatalf("VolumeTierMoveDatFromRemote missing-volume error mismatch: %v", err)
	}

	mismatchStream, err := grpcClient.VolumeTierMoveDatFromRemote(ctx, &volume_server_pb.VolumeTierMoveDatFromRemoteRequest{
		VolumeId:   volumeID,
		Collection: "wrong-collection",
	})
	if err == nil {
		_, err = mismatchStream.Recv()
	}
	if err == nil || !strings.Contains(err.Error(), "unexpected input") {
		t.Fatalf("VolumeTierMoveDatFromRemote collection mismatch error mismatch: %v", err)
	}

	localDiskStream, err := grpcClient.VolumeTierMoveDatFromRemote(ctx, &volume_server_pb.VolumeTierMoveDatFromRemoteRequest{
		VolumeId:   volumeID,
		Collection: collection,
	})
	if err == nil {
		_, err = localDiskStream.Recv()
	}
	if err == nil || !strings.Contains(err.Error(), "already on local disk") {
		t.Fatalf("VolumeTierMoveDatFromRemote local-disk error mismatch: %v", err)
	}
}
