package agate

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

	"gitea.unprism.ru/KRBL/Agate/store"
	"gitea.unprism.ru/KRBL/Agate/store/filesystem"
	"gitea.unprism.ru/KRBL/Agate/store/sqlite"
)

// setupTestEnvironment creates a temporary directory and initializes the stores
func setupTestEnvironment(t *testing.T) (string, store.MetadataStore, store.BlobStore, func()) {
	// Create a temporary directory for tests
	tempDir, err := os.MkdirTemp("", "agate-test-*")
	if err != nil {
		t.Fatalf("Failed to create temp directory: %v", err)
	}

	// Create directories for metadata and blobs
	metadataDir := filepath.Join(tempDir, "metadata")
	blobsDir := filepath.Join(tempDir, "blobs")
	if err := os.MkdirAll(metadataDir, 0755); err != nil {
		os.RemoveAll(tempDir)
		t.Fatalf("Failed to create metadata directory: %v", err)
	}
	if err := os.MkdirAll(blobsDir, 0755); err != nil {
		os.RemoveAll(tempDir)
		t.Fatalf("Failed to create blobs directory: %v", err)
	}

	// Initialize the stores
	dbPath := filepath.Join(metadataDir, "snapshots.db")
	metadataStore, err := sqlite.NewSQLiteStore(dbPath)
	if err != nil {
		os.RemoveAll(tempDir)
		t.Fatalf("Failed to create metadata store: %v", err)
	}

	blobStore, err := filesystem.NewFileSystemStore(blobsDir)
	if err != nil {
		metadataStore.Close()
		os.RemoveAll(tempDir)
		t.Fatalf("Failed to create blob store: %v", err)
	}

	// Return a cleanup function
	cleanup := func() {
		metadataStore.Close()
		os.RemoveAll(tempDir)
	}

	return tempDir, metadataStore, blobStore, cleanup
}

// createTestFiles creates test files in the specified directory
func createTestFiles(t *testing.T, dir string) {
	// Create a subdirectory
	subDir := filepath.Join(dir, "subdir")
	if err := os.MkdirAll(subDir, 0755); err != nil {
		t.Fatalf("Failed to create subdirectory: %v", err)
	}

	// Create some test files
	testFiles := map[string]string{
		filepath.Join(dir, "file1.txt"):       "This is file 1",
		filepath.Join(dir, "file2.txt"):       "This is file 2",
		filepath.Join(subDir, "subfile1.txt"): "This is subfile 1",
		filepath.Join(subDir, "subfile2.txt"): "This is subfile 2",
	}

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

func TestCreateAndGetSnapshot(t *testing.T) {
	tempDir, metadataStore, blobStore, cleanup := setupTestEnvironment(t)
	defer cleanup()

	// Create a source directory with test files
	sourceDir := filepath.Join(tempDir, "source")
	if err := os.MkdirAll(sourceDir, 0755); err != nil {
		t.Fatalf("Failed to create source directory: %v", err)
	}
	createTestFiles(t, sourceDir)

	// Create a snapshot manager
	manager, err := CreateSnapshotManager(metadataStore, blobStore)
	if err != nil {
		t.Fatalf("Failed to create snapshot manager: %v", err)
	}

	// Create a snapshot
	ctx := context.Background()
	snapshot, err := manager.CreateSnapshot(ctx, sourceDir, "Test Snapshot", "")
	if err != nil {
		t.Fatalf("Failed to create snapshot: %v", err)
	}

	// Check that the snapshot was created with the correct name
	if snapshot.Name != "Test Snapshot" {
		t.Errorf("Snapshot has wrong name: got %s, want %s", snapshot.Name, "Test Snapshot")
	}

	// Check that the snapshot has the correct number of files
	if len(snapshot.Files) != 5 { // 4 files + 1 directory
		t.Errorf("Snapshot has wrong number of files: got %d, want %d", len(snapshot.Files), 5)
	}

	// Get the snapshot details
	retrievedSnapshot, err := manager.GetSnapshotDetails(ctx, snapshot.ID)
	if err != nil {
		t.Fatalf("Failed to get snapshot details: %v", err)
	}

	// Check that the retrieved snapshot matches the original
	if retrievedSnapshot.ID != snapshot.ID {
		t.Errorf("Retrieved snapshot ID does not match: got %s, want %s", retrievedSnapshot.ID, snapshot.ID)
	}
	if retrievedSnapshot.Name != snapshot.Name {
		t.Errorf("Retrieved snapshot name does not match: got %s, want %s", retrievedSnapshot.Name, snapshot.Name)
	}
	if len(retrievedSnapshot.Files) != len(snapshot.Files) {
		t.Errorf("Retrieved snapshot has wrong number of files: got %d, want %d", len(retrievedSnapshot.Files), len(snapshot.Files))
	}
}

func TestListSnapshots(t *testing.T) {
	tempDir, metadataStore, blobStore, cleanup := setupTestEnvironment(t)
	defer cleanup()

	// Create a source directory with test files
	sourceDir := filepath.Join(tempDir, "source")
	if err := os.MkdirAll(sourceDir, 0755); err != nil {
		t.Fatalf("Failed to create source directory: %v", err)
	}
	createTestFiles(t, sourceDir)

	// Create a snapshot manager
	manager, err := CreateSnapshotManager(metadataStore, blobStore)
	if err != nil {
		t.Fatalf("Failed to create snapshot manager: %v", err)
	}

	// Create multiple snapshots
	ctx := context.Background()
	snapshot1, err := manager.CreateSnapshot(ctx, sourceDir, "Snapshot 1", "")
	if err != nil {
		t.Fatalf("Failed to create snapshot: %v", err)
	}

	// Modify a file
	if err := os.WriteFile(filepath.Join(sourceDir, "file1.txt"), []byte("Modified file 1"), 0644); err != nil {
		t.Fatalf("Failed to modify test file: %v", err)
	}

	snapshot2, err := manager.CreateSnapshot(ctx, sourceDir, "Snapshot 2", snapshot1.ID)
	if err != nil {
		t.Fatalf("Failed to create snapshot: %v", err)
	}

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

	// Check that both snapshots are listed
	if len(snapshots) != 2 {
		t.Errorf("Wrong number of snapshots listed: got %d, want %d", len(snapshots), 2)
	}

	// Check that the snapshots have the correct information
	for _, snap := range snapshots {
		if snap.ID == snapshot1.ID {
			if snap.Name != "Snapshot 1" {
				t.Errorf("Snapshot 1 has wrong name: got %s, want %s", snap.Name, "Snapshot 1")
			}
			if snap.ParentID != "" {
				t.Errorf("Snapshot 1 has wrong parent ID: got %s, want %s", snap.ParentID, "")
			}
		} else if snap.ID == snapshot2.ID {
			if snap.Name != "Snapshot 2" {
				t.Errorf("Snapshot 2 has wrong name: got %s, want %s", snap.Name, "Snapshot 2")
			}
			if snap.ParentID != snapshot1.ID {
				t.Errorf("Snapshot 2 has wrong parent ID: got %s, want %s", snap.ParentID, snapshot1.ID)
			}
		} else {
			t.Errorf("Unexpected snapshot ID: %s", snap.ID)
		}
	}
}

func TestDeleteSnapshot(t *testing.T) {
	tempDir, metadataStore, blobStore, cleanup := setupTestEnvironment(t)
	defer cleanup()

	// Create a source directory with test files
	sourceDir := filepath.Join(tempDir, "source")
	if err := os.MkdirAll(sourceDir, 0755); err != nil {
		t.Fatalf("Failed to create source directory: %v", err)
	}
	createTestFiles(t, sourceDir)

	// Create a snapshot manager
	manager, err := CreateSnapshotManager(metadataStore, blobStore)
	if err != nil {
		t.Fatalf("Failed to create snapshot manager: %v", err)
	}

	// Create a snapshot
	ctx := context.Background()
	snapshot, err := manager.CreateSnapshot(ctx, sourceDir, "Test Snapshot", "")
	if err != nil {
		t.Fatalf("Failed to create snapshot: %v", err)
	}

	// Delete the snapshot
	err = manager.DeleteSnapshot(ctx, snapshot.ID)
	if err != nil {
		t.Fatalf("Failed to delete snapshot: %v", err)
	}

	// Try to get the deleted snapshot
	_, err = manager.GetSnapshotDetails(ctx, snapshot.ID)
	if err == nil {
		t.Fatalf("Expected error when getting deleted snapshot, got nil")
	}

	// List snapshots to confirm it's gone
	snapshots, err := manager.ListSnapshots(ctx)
	if err != nil {
		t.Fatalf("Failed to list snapshots: %v", err)
	}
	if len(snapshots) != 0 {
		t.Errorf("Expected 0 snapshots after deletion, got %d", len(snapshots))
	}
}

func TestOpenFile(t *testing.T) {
	_, metadataStore, blobStore, cleanup := setupTestEnvironment(t)
	defer cleanup()

	// Create a source directory with test files
	sourceDir := filepath.Join(blobStore.GetActiveDir(), "source")
	if err := os.MkdirAll(sourceDir, 0755); err != nil {
		t.Fatalf("Failed to create source directory: %v", err)
	}
	createTestFiles(t, sourceDir)

	// Create a snapshot manager
	manager, err := CreateSnapshotManager(metadataStore, blobStore)
	if err != nil {
		t.Fatalf("Failed to create snapshot manager: %v", err)
	}

	// Create a snapshot
	ctx := context.Background()
	snapshot, err := manager.CreateSnapshot(ctx, sourceDir, "Test Snapshot", "")
	if err != nil {
		t.Fatalf("Failed to create snapshot: %v", err)
	}

	// Open a file from the snapshot
	fileReader, err := manager.OpenFile(ctx, snapshot.ID, "file1.txt")
	if err != nil {
		t.Fatalf("Failed to open file from snapshot: %v", err)
	}
	defer fileReader.Close()

	// Read the file content
	content, err := io.ReadAll(fileReader)
	if err != nil {
		t.Fatalf("Failed to read file content: %v", err)
	}

	// Check that the content matches the original
	if string(content) != "This is file 1" {
		t.Errorf("File content does not match: got %s, want %s", string(content), "This is file 1")
	}

	// Try to open a non-existent file
	pipe, err := manager.OpenFile(ctx, snapshot.ID, "nonexistent.txt")
	if err == nil {
		tmp := make([]byte, 1)
		_, err = pipe.Read(tmp)

		if err == nil {
			t.Fatalf("Expected error when opening non-existent file, got nil")
		}
	}
}

func TestExtractSnapshot(t *testing.T) {
	tempDir, metadataStore, blobStore, cleanup := setupTestEnvironment(t)
	defer cleanup()

	// Create a source directory with test files
	sourceDir := filepath.Join(tempDir, "source")
	if err := os.MkdirAll(sourceDir, 0755); err != nil {
		t.Fatalf("Failed to create source directory: %v", err)
	}
	createTestFiles(t, sourceDir)

	// Create a snapshot manager
	manager, err := CreateSnapshotManager(metadataStore, blobStore)
	if err != nil {
		t.Fatalf("Failed to create snapshot manager: %v", err)
	}

	// Create a snapshot
	ctx := context.Background()
	snapshot, err := manager.CreateSnapshot(ctx, sourceDir, "Test Snapshot", "")
	if err != nil {
		t.Fatalf("Failed to create snapshot: %v", err)
	}

	// Create a target directory for extraction
	targetDir := filepath.Join(tempDir, "target")
	if err := os.MkdirAll(targetDir, 0755); err != nil {
		t.Fatalf("Failed to create target directory: %v", err)
	}

	// Extract the snapshot
	err = manager.ExtractSnapshot(ctx, snapshot.ID, targetDir)
	if err != nil {
		t.Fatalf("Failed to extract snapshot: %v", err)
	}

	// Check that the files were extracted correctly
	testFiles := map[string]string{
		filepath.Join(targetDir, "file1.txt"):           "This is file 1",
		filepath.Join(targetDir, "file2.txt"):           "This is file 2",
		filepath.Join(targetDir, "subdir/subfile1.txt"): "This is subfile 1",
		filepath.Join(targetDir, "subdir/subfile2.txt"): "This is subfile 2",
	}

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

	// Try to extract a non-existent snapshot
	err = manager.ExtractSnapshot(ctx, "nonexistent-id", targetDir)
	if err == nil {
		t.Fatalf("Expected error when extracting non-existent snapshot, got nil")
	}
}

func TestUpdateSnapshotMetadata(t *testing.T) {
	tempDir, metadataStore, blobStore, cleanup := setupTestEnvironment(t)
	defer cleanup()

	// Create a source directory with test files
	sourceDir := filepath.Join(tempDir, "source")
	if err := os.MkdirAll(sourceDir, 0755); err != nil {
		t.Fatalf("Failed to create source directory: %v", err)
	}
	createTestFiles(t, sourceDir)

	// Create a snapshot manager
	manager, err := CreateSnapshotManager(metadataStore, blobStore)
	if err != nil {
		t.Fatalf("Failed to create snapshot manager: %v", err)
	}

	// Create a snapshot
	ctx := context.Background()
	snapshot, err := manager.CreateSnapshot(ctx, sourceDir, "Test Snapshot", "")
	if err != nil {
		t.Fatalf("Failed to create snapshot: %v", err)
	}

	// Update the snapshot metadata
	newName := "Updated Snapshot Name"
	err = manager.UpdateSnapshotMetadata(ctx, snapshot.ID, newName)
	if err != nil {
		t.Fatalf("Failed to update snapshot metadata: %v", err)
	}

	// Get the updated snapshot
	updatedSnapshot, err := manager.GetSnapshotDetails(ctx, snapshot.ID)
	if err != nil {
		t.Fatalf("Failed to get updated snapshot: %v", err)
	}

	// Check that the name was updated
	if updatedSnapshot.Name != newName {
		t.Errorf("Snapshot name was not updated: got %s, want %s", updatedSnapshot.Name, newName)
	}

	// Try to update a non-existent snapshot
	err = manager.UpdateSnapshotMetadata(ctx, "nonexistent-id", "New Name")
	if err == nil {
		t.Fatalf("Expected error when updating non-existent snapshot, got nil")
	}
}