package agate

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

	"gitea.unprism.ru/KRBL/Agate/remote"
	"gitea.unprism.ru/KRBL/Agate/store"
)

// TestGRPCServerClient tests the interaction between a gRPC server and client.
// It creates multiple snapshots with different content on the server,
// connects a client to the server, downloads the latest snapshot,
// and verifies the contents of the files.
func TestGRPCServerClient(t *testing.T) {
	// Skip this test in short mode
	if testing.Short() {
		t.Skip("Skipping gRPC server-client test in short mode")
	}

	// Create a temporary directory for the server
	serverDir, err := os.MkdirTemp("", "agate-server-*")
	if err != nil {
		t.Fatalf("Failed to create server temp directory: %v", err)
	}
	defer os.RemoveAll(serverDir)

	// Create a temporary directory for the client
	clientDir, err := os.MkdirTemp("", "agate-client-*")
	if err != nil {
		t.Fatalf("Failed to create client temp directory: %v", err)
	}
	defer os.RemoveAll(clientDir)

	// Create Agate options for the server
	serverOptions := AgateOptions{
		WorkDir: serverDir,
	}

	// Create Agate instance for the server
	serverAgate, err := New(serverOptions)
	if err != nil {
		t.Fatalf("Failed to create server Agate instance: %v", err)
	}
	defer serverAgate.Close()

	// Create a data directory
	dataDir := serverAgate.options.BlobStore.GetActiveDir()
	if err := os.MkdirAll(dataDir, 0755); err != nil {
		t.Fatalf("Failed to create data directory: %v", err)
	}

	// Create initial test files for the first snapshot
	initialFiles := map[string]string{
		filepath.Join(dataDir, "file1.txt"):           "Initial content of file 1",
		filepath.Join(dataDir, "file2.txt"):           "Initial content of file 2",
		filepath.Join(dataDir, "subdir", "file3.txt"): "Initial content of file 3",
	}

	// Create subdirectory
	if err := os.MkdirAll(filepath.Join(dataDir, "subdir"), 0755); err != nil {
		t.Fatalf("Failed to create subdirectory: %v", err)
	}

	// Create the files
	for path, content := range initialFiles {
		if err := os.WriteFile(path, []byte(content), 0644); err != nil {
			t.Fatalf("Failed to create test file %s: %v", path, err)
		}
	}

	// Create the first snapshot
	ctx := context.Background()
	snapshot1ID, err := serverAgate.SaveSnapshot(ctx, "Snapshot 1", "")
	if err != nil {
		t.Fatalf("Failed to create first snapshot: %v", err)
	}
	t.Logf("Created first snapshot with ID: %s", snapshot1ID)

	// Modify some files and add a new file for the second snapshot
	modifiedFiles := map[string]string{
		filepath.Join(dataDir, "file1.txt"): "Modified content of file 1",
		filepath.Join(dataDir, "file4.txt"): "Content of new file 4",
	}

	for path, content := range modifiedFiles {
		if err := os.WriteFile(path, []byte(content), 0644); err != nil {
			t.Fatalf("Failed to modify/create test file %s: %v", path, err)
		}
	}

	// Create the second snapshot
	snapshot2ID, err := serverAgate.SaveSnapshot(ctx, "Snapshot 2", snapshot1ID)
	if err != nil {
		t.Fatalf("Failed to create second snapshot: %v", err)
	}
	t.Logf("Created second snapshot with ID: %s", snapshot2ID)

	// Delete a file and modify another for the third snapshot
	if err := os.Remove(filepath.Join(dataDir, "file2.txt")); err != nil {
		t.Fatalf("Failed to delete test file: %v", err)
	}

	if err := os.WriteFile(filepath.Join(dataDir, "subdir/file3.txt"), []byte("Modified content of file 3"), 0644); err != nil {
		t.Fatalf("Failed to modify test file: %v", err)
	}

	// Create the third snapshot
	snapshot3ID, err := serverAgate.SaveSnapshot(ctx, "Snapshot 3", snapshot2ID)
	if err != nil {
		t.Fatalf("Failed to create third snapshot: %v", err)
	}
	t.Logf("Created third snapshot with ID: %s", snapshot3ID)

	// Start the gRPC server
	serverAddress := "localhost:50051"
	server, err := remote.RunServer(ctx, serverAgate.manager, serverAddress)
	if err != nil {
		t.Fatalf("Failed to start gRPC server: %v", err)
	}
	defer server.Stop(ctx)

	// Give the server a moment to start
	time.Sleep(100 * time.Millisecond)

	// Connect a client to the server
	client, err := remote.NewClient(serverAddress)
	if err != nil {
		t.Fatalf("Failed to connect client to server: %v", err)
	}
	defer client.Close()

	// List snapshots from the client
	snapshots, err := client.ListSnapshots(ctx)
	if err != nil {
		t.Fatalf("Failed to list snapshots from client: %v", err)
	}

	// Verify we have 3 snapshots
	if len(snapshots) != 3 {
		t.Errorf("Expected 3 snapshots, got %d", len(snapshots))
	}

	// Find the latest snapshot (should be snapshot3)
	var latestSnapshot store.SnapshotInfo
	for _, snapshot := range snapshots {
		if latestSnapshot.CreationTime.Before(snapshot.CreationTime) {
			latestSnapshot = snapshot
		}
	}

	// Verify the latest snapshot is snapshot3
	if latestSnapshot.ID != snapshot3ID {
		t.Errorf("Latest snapshot ID is %s, expected %s", latestSnapshot.ID, snapshot3ID)
	}

	// Get detailed information about the latest snapshot
	snapshotDetails, err := client.FetchSnapshotDetails(ctx, latestSnapshot.ID)
	if err != nil {
		t.Fatalf("Failed to fetch snapshot details: %v", err)
	}

	// Verify the snapshot details
	if snapshotDetails.ID != snapshot3ID {
		t.Errorf("Snapshot details ID is %s, expected %s", snapshotDetails.ID, snapshot3ID)
	}

	// Create a directory to download the snapshot to
	downloadDir := filepath.Join(clientDir, "download")
	if err := os.MkdirAll(downloadDir, 0755); err != nil {
		t.Fatalf("Failed to create download directory: %v", err)
	}

	// Download the snapshot
	err = client.DownloadSnapshot(ctx, latestSnapshot.ID, downloadDir, "")
	if err != nil {
		t.Fatalf("Failed to download snapshot: %v", err)
	}

	// Verify the downloaded files match the expected content
	expectedFiles := map[string]string{
		filepath.Join(downloadDir, "file1.txt"):        "Modified content of file 1",
		filepath.Join(downloadDir, "file4.txt"):        "Content of new file 4",
		filepath.Join(downloadDir, "subdir/file3.txt"): "Modified content of file 3",
	}

	for path, expectedContent := range expectedFiles {
		content, err := os.ReadFile(path)
		if err != nil {
			t.Fatalf("Failed to read downloaded file %s: %v", path, err)
		}
		if string(content) != expectedContent {
			t.Errorf("Downloaded file %s has wrong content: got %s, want %s", path, string(content), expectedContent)
		}
	}

	// Verify that file2.txt doesn't exist in the downloaded snapshot
	if _, err := os.Stat(filepath.Join(downloadDir, "file2.txt")); !os.IsNotExist(err) {
		t.Errorf("file2.txt should not exist in the downloaded snapshot")
	}
}