//go:build integration
// +build integration

package daemon

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

	"github.com/steveyegge/beads/internal/rpc"
	"github.com/steveyegge/beads/internal/storage/sqlite"
)

func TestDiscoverDaemon(t *testing.T) {
	tmpDir := t.TempDir()
	workspace := filepath.Join(tmpDir, ".beads")
	os.MkdirAll(workspace, 0755)

	// Start daemon
	dbPath := filepath.Join(workspace, "test.db")
	socketPath := filepath.Join(workspace, "bd.sock")
	store, err := sqlite.New(context.Background(), dbPath)
	if err != nil {
		t.Fatalf("failed to create storage: %v", err)
	}
	defer store.Close()

	server := rpc.NewServer(socketPath, store, tmpDir, dbPath)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go server.Start(ctx)
	<-server.WaitReady()
	defer server.Stop()

	// Test discoverDaemon directly
	daemon := discoverDaemon(socketPath)
	if !daemon.Alive {
		t.Errorf("daemon not alive: %s", daemon.Error)
	}
	if daemon.PID != os.Getpid() {
		t.Errorf("wrong PID: expected %d, got %d", os.Getpid(), daemon.PID)
	}
	if daemon.UptimeSeconds <= 0 {
		t.Errorf("invalid uptime: %f", daemon.UptimeSeconds)
	}
	if daemon.WorkspacePath != tmpDir {
		t.Errorf("wrong workspace: expected %s, got %s", tmpDir, daemon.WorkspacePath)
	}
}

func TestFindDaemonByWorkspace(t *testing.T) {
	tmpDir := t.TempDir()
	workspace := filepath.Join(tmpDir, ".beads")
	os.MkdirAll(workspace, 0755)

	// Start daemon
	dbPath := filepath.Join(workspace, "test.db")
	socketPath := filepath.Join(workspace, "bd.sock")
	store, err := sqlite.New(context.Background(), dbPath)
	if err != nil {
		t.Fatalf("failed to create storage: %v", err)
	}
	defer store.Close()

	server := rpc.NewServer(socketPath, store, tmpDir, dbPath)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go server.Start(ctx)
	<-server.WaitReady()
	defer server.Stop()

	// Find daemon by workspace
	daemon, err := FindDaemonByWorkspace(tmpDir)
	if err != nil {
		t.Fatalf("failed to find daemon: %v", err)
	}
	if daemon == nil {
		t.Fatal("daemon not found")
	}
	if !daemon.Alive {
		t.Errorf("daemon not alive: %s", daemon.Error)
	}
	if daemon.WorkspacePath != tmpDir {
		t.Errorf("wrong workspace: expected %s, got %s", tmpDir, daemon.WorkspacePath)
	}
}

func TestCleanupStaleSockets(t *testing.T) {
	tmpDir := t.TempDir()

	// Create stale socket file
	stalePath := filepath.Join(tmpDir, "stale.sock")
	if err := os.WriteFile(stalePath, []byte{}, 0644); err != nil {
		t.Fatalf("failed to create stale socket: %v", err)
	}

	daemons := []DaemonInfo{
		{
			SocketPath: stalePath,
			Alive:      false,
		},
	}

	cleaned, err := CleanupStaleSockets(daemons)
	if err != nil {
		t.Fatalf("cleanup failed: %v", err)
	}
	if cleaned != 1 {
		t.Errorf("expected 1 cleaned, got %d", cleaned)
	}

	// Verify socket was removed
	if _, err := os.Stat(stalePath); !os.IsNotExist(err) {
		t.Error("stale socket still exists")
	}
}

func TestWalkWithDepth(t *testing.T) {
	tmpDir := t.TempDir()

	// Create test directory structure
	// tmpDir/
	//   file1.txt
	//   dir1/
	//     file2.txt
	//     dir2/
	//       file3.txt
	//       dir3/
	//         file4.txt

	os.WriteFile(filepath.Join(tmpDir, "file1.txt"), []byte("test"), 0644)
	os.MkdirAll(filepath.Join(tmpDir, "dir1", "dir2", "dir3"), 0755)
	os.WriteFile(filepath.Join(tmpDir, "dir1", "file2.txt"), []byte("test"), 0644)
	os.WriteFile(filepath.Join(tmpDir, "dir1", "dir2", "file3.txt"), []byte("test"), 0644)
	os.WriteFile(filepath.Join(tmpDir, "dir1", "dir2", "dir3", "file4.txt"), []byte("test"), 0644)

	tests := []struct {
		name      string
		maxDepth  int
		wantFiles int
	}{
		{"depth 0", 0, 1},        // Only file1.txt
		{"depth 1", 1, 2},        // file1.txt, file2.txt
		{"depth 2", 2, 3},        // file1.txt, file2.txt, file3.txt
		{"depth 3", 3, 4},        // All files
		{"depth 10", 10, 4},      // All files (max depth not reached)
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var foundFiles []string
			fn := func(path string, info os.FileInfo) error {
				if !info.IsDir() {
					foundFiles = append(foundFiles, path)
				}
				return nil
			}

			err := walkWithDepth(tmpDir, 0, tt.maxDepth, fn)
			if err != nil {
				t.Fatalf("walkWithDepth failed: %v", err)
			}

			if len(foundFiles) != tt.wantFiles {
				t.Errorf("Expected %d files, got %d: %v", tt.wantFiles, len(foundFiles), foundFiles)
			}
		})
	}
}

func TestWalkWithDepth_SkipsHiddenDirs(t *testing.T) {
	tmpDir := t.TempDir()

	// Create hidden directories (should skip)
	os.MkdirAll(filepath.Join(tmpDir, ".git"), 0755)
	os.MkdirAll(filepath.Join(tmpDir, ".hidden"), 0755)
	os.MkdirAll(filepath.Join(tmpDir, "node_modules"), 0755)
	os.MkdirAll(filepath.Join(tmpDir, "vendor"), 0755)

	// Create .beads directory (should NOT skip)
	os.MkdirAll(filepath.Join(tmpDir, ".beads"), 0755)

	// Add files
	os.WriteFile(filepath.Join(tmpDir, ".git", "config"), []byte("test"), 0644)
	os.WriteFile(filepath.Join(tmpDir, ".hidden", "secret"), []byte("test"), 0644)
	os.WriteFile(filepath.Join(tmpDir, "node_modules", "package.json"), []byte("test"), 0644)
	os.WriteFile(filepath.Join(tmpDir, ".beads", "beads.db"), []byte("test"), 0644)

	var foundFiles []string
	fn := func(path string, info os.FileInfo) error {
		if !info.IsDir() {
			foundFiles = append(foundFiles, filepath.Base(path))
		}
		return nil
	}

	err := walkWithDepth(tmpDir, 0, 5, fn)
	if err != nil {
		t.Fatalf("walkWithDepth failed: %v", err)
	}

	// Should only find beads.db from .beads directory
	if len(foundFiles) != 1 || foundFiles[0] != "beads.db" {
		t.Errorf("Expected only beads.db, got: %v", foundFiles)
	}
}

func TestDiscoverDaemons_Registry(t *testing.T) {
	// Test registry-based discovery (no search roots)
	daemons, err := DiscoverDaemons(nil)
	if err != nil {
		t.Fatalf("DiscoverDaemons failed: %v", err)
	}

	// Should return empty list (no daemons running in test environment)
	// Just verify it doesn't error
	_ = daemons
}

func TestDiscoverDaemons_Legacy(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping slow daemon discovery test in short mode")
	}
	tmpDir := t.TempDir()
	beadsDir := filepath.Join(tmpDir, ".beads")
	os.MkdirAll(beadsDir, 0755)

	// Start a test daemon
	dbPath := filepath.Join(beadsDir, "test.db")
	socketPath := filepath.Join(beadsDir, "bd.sock")
	store, err := sqlite.New(context.Background(), dbPath)
	if err != nil {
		t.Fatalf("failed to create storage: %v", err)
	}
	defer store.Close()

	server := rpc.NewServer(socketPath, store, tmpDir, dbPath)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go server.Start(ctx)
	<-server.WaitReady()
	defer server.Stop()

	// Test legacy discovery with explicit search roots
	daemons, err := DiscoverDaemons([]string{tmpDir})
	if err != nil {
		t.Fatalf("DiscoverDaemons failed: %v", err)
	}

	if len(daemons) != 1 {
		t.Fatalf("Expected 1 daemon, got %d", len(daemons))
	}

	daemon := daemons[0]
	if !daemon.Alive {
		t.Errorf("Daemon not alive: %s", daemon.Error)
	}
	if daemon.WorkspacePath != tmpDir {
		t.Errorf("Wrong workspace path: expected %s, got %s", tmpDir, daemon.WorkspacePath)
	}
}

func TestCheckDaemonErrorFile(t *testing.T) {
	tmpDir := t.TempDir()
	beadsDir := filepath.Join(tmpDir, ".beads")
	os.MkdirAll(beadsDir, 0755)
	socketPath := filepath.Join(beadsDir, "bd.sock")

	// Test 1: No error file exists
	errMsg := checkDaemonErrorFile(socketPath)
	if errMsg != "" {
		t.Errorf("Expected empty error message, got: %s", errMsg)
	}

	// Test 2: Error file exists with content
	errorFilePath := filepath.Join(beadsDir, "daemon-error")
	expectedError := "failed to start: database locked"
	os.WriteFile(errorFilePath, []byte(expectedError), 0644)

	errMsg = checkDaemonErrorFile(socketPath)
	if errMsg != expectedError {
		t.Errorf("Expected error message %q, got %q", expectedError, errMsg)
	}
}

func TestStopDaemon_NotAlive(t *testing.T) {
	daemon := DaemonInfo{
		Alive: false,
	}

	err := StopDaemon(daemon)
	if err == nil {
		t.Error("Expected error when stopping non-alive daemon")
	}
	if err.Error() != "daemon is not running" {
		t.Errorf("Unexpected error message: %s", err.Error())
	}
}

func TestKillAllDaemons_Empty(t *testing.T) {
	results := KillAllDaemons([]DaemonInfo{}, false)
	if results.Stopped != 0 || results.Failed != 0 {
		t.Errorf("Expected 0 stopped and 0 failed, got %d stopped and %d failed", results.Stopped, results.Failed)
	}
	if len(results.Failures) != 0 {
		t.Errorf("Expected empty failures list, got %d failures", len(results.Failures))
	}
}

func TestKillAllDaemons_NotAlive(t *testing.T) {
	daemons := []DaemonInfo{
		{Alive: false, WorkspacePath: "/test", PID: 12345},
	}

	results := KillAllDaemons(daemons, false)
	if results.Stopped != 0 || results.Failed != 0 {
		t.Errorf("Expected 0 stopped and 0 failed for dead daemon, got %d stopped and %d failed", results.Stopped, results.Failed)
	}
}

func TestFindDaemonByWorkspace_NotFound(t *testing.T) {
	tmpDir := t.TempDir()

	// Try to find daemon in directory without any daemon
	daemon, err := FindDaemonByWorkspace(tmpDir)
	if err == nil {
		t.Error("Expected error when daemon not found")
	}
	if daemon != nil {
		t.Error("Expected nil daemon when not found")
	}
}

func TestDiscoverDaemon_SocketMissing(t *testing.T) {
	tmpDir := t.TempDir()
	socketPath := filepath.Join(tmpDir, "nonexistent.sock")

	// Try to discover daemon on non-existent socket
	daemon := discoverDaemon(socketPath)
	if daemon.Alive {
		t.Error("Expected daemon to not be alive for missing socket")
	}
	if daemon.SocketPath != socketPath {
		t.Errorf("Expected socket path %s, got %s", socketPath, daemon.SocketPath)
	}
	if daemon.Error == "" {
		t.Error("Expected error message when daemon not found")
	}
}

func TestCleanupStaleSockets_AlreadyRemoved(t *testing.T) {
	tmpDir := t.TempDir()

	// Create stale daemon with non-existent socket
	stalePath := filepath.Join(tmpDir, "nonexistent.sock")

	daemons := []DaemonInfo{
		{
			SocketPath: stalePath,
			Alive:      false,
		},
	}

	// Should succeed even if socket doesn't exist
	cleaned, err := CleanupStaleSockets(daemons)
	if err != nil {
		t.Fatalf("cleanup failed: %v", err)
	}
	if cleaned != 0 {
		t.Errorf("expected 0 cleaned (socket didn't exist), got %d", cleaned)
	}
}

func TestCleanupStaleSockets_AliveDaemon(t *testing.T) {
	tmpDir := t.TempDir()
	socketPath := filepath.Join(tmpDir, "alive.sock")

	daemons := []DaemonInfo{
		{
			SocketPath: socketPath,
			Alive:      true,
		},
	}

	// Should not remove socket for alive daemon
	cleaned, err := CleanupStaleSockets(daemons)
	if err != nil {
		t.Fatalf("cleanup failed: %v", err)
	}
	if cleaned != 0 {
		t.Errorf("expected 0 cleaned (daemon alive), got %d", cleaned)
	}
}
