package browser

import (
	"context"
	"sync"
	"testing"
	"time"
)

func TestManagerNew(t *testing.T) {
	m := NewManager()
	if m == nil {
		t.Fatal("NewManager returned nil")
	}
	if m.ActiveCount() != 0 {
		t.Errorf("ActiveCount() = %d, want 0", m.ActiveCount())
	}
	if m.TotalStarted() != 0 {
		t.Errorf("TotalStarted() = %d, want 0", m.TotalStarted())
	}
}

func TestManagerStartInvalidBinary(t *testing.T) {
	m := NewManager()
	ctx := context.Background()

	config := Config{
		ID:         "test-browser",
		URL:        "http://localhost:3000",
		BinaryPath: "/nonexistent/chrome",
		Headless:   true,
	}

	_, err := m.Start(ctx, "test-browser", config)
	if err == nil {
		t.Fatal("expected error for nonexistent binary")
	}

	if m.ActiveCount() != 0 {
		t.Errorf("ActiveCount() = %d, want 0 after failed start", m.ActiveCount())
	}
}

func TestManagerDuplicateID(t *testing.T) {
	m := NewManager()
	ctx := context.Background()

	// Try to create two browsers with the same ID
	// Since we don't have Chrome installed, both should fail at binary lookup
	// But we can test the LoadOrStore logic by mocking

	// Store a placeholder
	m.browsers.Store("dup-id", &Browser{config: Config{ID: "dup-id"}})

	config := Config{
		ID:       "dup-id",
		URL:      "http://localhost:3000",
		Headless: true,
	}

	_, err := m.Start(ctx, "dup-id", config)
	if err != ErrBrowserExists {
		t.Errorf("expected ErrBrowserExists, got %v", err)
	}
}

func TestManagerGetNotFound(t *testing.T) {
	m := NewManager()

	_, err := m.Get("nonexistent")
	if err != ErrBrowserNotFound {
		t.Errorf("expected ErrBrowserNotFound, got %v", err)
	}
}

func TestManagerGetExact(t *testing.T) {
	m := NewManager()

	// Store directly for testing
	b := &Browser{config: Config{ID: "test-id"}}
	m.browsers.Store("test-id", b)

	found, err := m.Get("test-id")
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if found != b {
		t.Error("Get returned different browser")
	}
}

func TestManagerGetFuzzy(t *testing.T) {
	m := NewManager()

	// Store browser with compound ID
	b := &Browser{config: Config{ID: "project-abc:test-browser", Path: "/home/test"}}
	m.browsers.Store("project-abc:test-browser", b)

	// Fuzzy lookup by component
	found, err := m.Get("test-browser")
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if found != b {
		t.Error("Get fuzzy match returned wrong browser")
	}
}

func TestManagerGetFuzzyAmbiguous(t *testing.T) {
	m := NewManager()

	// Store two browsers with same component name
	b1 := &Browser{config: Config{ID: "proj1:browser", Path: "/home/proj1"}}
	b2 := &Browser{config: Config{ID: "proj2:browser", Path: "/home/proj2"}}
	m.browsers.Store("proj1:browser", b1)
	m.browsers.Store("proj2:browser", b2)

	_, err := m.Get("browser")
	if err != ErrBrowserAmbiguous {
		t.Errorf("expected ErrBrowserAmbiguous, got %v", err)
	}
}

func TestManagerGetWithPathFilter(t *testing.T) {
	m := NewManager()

	// Store two browsers with same component name but different paths
	b1 := &Browser{config: Config{ID: "proj1:browser", Path: "/home/proj1"}}
	b2 := &Browser{config: Config{ID: "proj2:browser", Path: "/home/proj2"}}
	m.browsers.Store("proj1:browser", b1)
	m.browsers.Store("proj2:browser", b2)

	// Filter by path should find only one
	found, err := m.GetWithPathFilter("browser", "/home/proj1")
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if found != b1 {
		t.Error("GetWithPathFilter returned wrong browser")
	}
}

func TestManagerList(t *testing.T) {
	m := NewManager()

	// Store some browsers
	b1 := &Browser{config: Config{ID: "b1", Path: "/home/proj1"}}
	b2 := &Browser{config: Config{ID: "b2", Path: "/home/proj2"}}
	m.browsers.Store("b1", b1)
	m.browsers.Store("b2", b2)

	infos := m.List()
	if len(infos) != 2 {
		t.Errorf("List() returned %d items, want 2", len(infos))
	}
}

func TestManagerListByPath(t *testing.T) {
	m := NewManager()

	// Store browsers with different paths
	b1 := &Browser{config: Config{ID: "b1", Path: "/home/proj1"}}
	b2 := &Browser{config: Config{ID: "b2", Path: "/home/proj2"}}
	b3 := &Browser{config: Config{ID: "b3", Path: "/home/proj1"}}
	m.browsers.Store("b1", b1)
	m.browsers.Store("b2", b2)
	m.browsers.Store("b3", b3)

	infos := m.ListByPath("/home/proj1")
	if len(infos) != 2 {
		t.Errorf("ListByPath() returned %d items, want 2", len(infos))
	}
}

func TestManagerStopNotFound(t *testing.T) {
	m := NewManager()
	ctx := context.Background()

	err := m.Stop(ctx, "nonexistent")
	if err != ErrBrowserNotFound {
		t.Errorf("expected ErrBrowserNotFound, got %v", err)
	}
}

func TestManagerShutdown(t *testing.T) {
	m := NewManager()
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	err := m.Shutdown(ctx)
	if err != nil {
		t.Errorf("Shutdown() error: %v", err)
	}

	// After shutdown, new starts should fail
	if !m.shuttingDown.Load() {
		t.Error("shuttingDown should be true after Shutdown")
	}
}

func TestManagerConcurrentAccess(t *testing.T) {
	m := NewManager()

	// Test concurrent reads/writes to the manager
	var wg sync.WaitGroup

	// Concurrent list operations
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_ = m.List()
			_ = m.ActiveCount()
		}()
	}

	// Concurrent store/load operations
	for i := 0; i < 10; i++ {
		id := string(rune('a' + i))
		wg.Add(1)
		go func(id string) {
			defer wg.Done()
			b := &Browser{config: Config{ID: id}}
			m.browsers.Store(id, b)
			m.browsers.Load(id)
			m.browsers.Delete(id)
		}(id)
	}

	wg.Wait()
}

func TestNormalizePath(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{"", ""},
		{"/home/user", "/home/user"},
		{"/home/user/", "/home/user"},
		{"/home/user///", "/home/user"},
		{".", "."},
	}

	for _, tc := range tests {
		result := normalizePath(tc.input)
		if result != tc.expected {
			t.Errorf("normalizePath(%q) = %q, want %q", tc.input, result, tc.expected)
		}
	}
}
