package memory

import (
	"context"
	"sync"
	"testing"

	"github.com/orchard9/rdev/internal/domain"
)

func TestProjectRepository_RegisterAndGet(t *testing.T) {
	repo := NewProjectRepository()
	ctx := context.Background()

	project := &domain.Project{
		ID:          "test-project",
		Name:        "Test Project",
		Description: "A test project",
		PodName:     "test-pod-0",
		Workspace:   "/workspace",
		Status:      domain.ProjectStatusRunning,
	}

	// Register
	if err := repo.Register(ctx, project); err != nil {
		t.Fatalf("Register() error = %v", err)
	}

	// Get
	retrieved, err := repo.Get(ctx, "test-project")
	if err != nil {
		t.Fatalf("Get() error = %v", err)
	}

	if retrieved.ID != project.ID {
		t.Errorf("ID = %q, want %q", retrieved.ID, project.ID)
	}
	if retrieved.Name != project.Name {
		t.Errorf("Name = %q, want %q", retrieved.Name, project.Name)
	}
	if retrieved.Status != project.Status {
		t.Errorf("Status = %q, want %q", retrieved.Status, project.Status)
	}
}

func TestProjectRepository_GetNotFound(t *testing.T) {
	repo := NewProjectRepository()
	ctx := context.Background()

	_, err := repo.Get(ctx, "nonexistent")
	if err != domain.ErrProjectNotFound {
		t.Errorf("Get() error = %v, want %v", err, domain.ErrProjectNotFound)
	}
}

func TestProjectRepository_List(t *testing.T) {
	repo := NewProjectRepository()
	ctx := context.Background()

	// Empty list initially
	projects, err := repo.List(ctx)
	if err != nil {
		t.Fatalf("List() error = %v", err)
	}
	if len(projects) != 0 {
		t.Errorf("Initial List() length = %d, want 0", len(projects))
	}

	// Register some projects
	for i := 0; i < 3; i++ {
		p := &domain.Project{
			ID:   domain.ProjectID("project-" + string(rune('a'+i))),
			Name: "Project " + string(rune('A'+i)),
		}
		_ = repo.Register(ctx, p)
	}

	// List should return all
	projects, err = repo.List(ctx)
	if err != nil {
		t.Fatalf("List() error = %v", err)
	}
	if len(projects) != 3 {
		t.Errorf("List() length = %d, want 3", len(projects))
	}
}

func TestProjectRepository_Exists(t *testing.T) {
	repo := NewProjectRepository()
	ctx := context.Background()

	project := &domain.Project{
		ID:   "existing-project",
		Name: "Existing",
	}
	_ = repo.Register(ctx, project)

	tests := []struct {
		id   domain.ProjectID
		want bool
	}{
		{"existing-project", true},
		{"nonexistent", false},
	}

	for _, tt := range tests {
		exists, err := repo.Exists(ctx, tt.id)
		if err != nil {
			t.Errorf("Exists(%q) error = %v", tt.id, err)
		}
		if exists != tt.want {
			t.Errorf("Exists(%q) = %v, want %v", tt.id, exists, tt.want)
		}
	}
}

func TestProjectRepository_Unregister(t *testing.T) {
	repo := NewProjectRepository()
	ctx := context.Background()

	project := &domain.Project{
		ID:   "to-remove",
		Name: "To Remove",
	}
	repo.Register(ctx, project)

	// Verify it exists
	exists, _ := repo.Exists(ctx, "to-remove")
	if !exists {
		t.Fatal("Project should exist after register")
	}

	// Unregister
	if err := repo.Unregister(ctx, "to-remove"); err != nil {
		t.Fatalf("Unregister() error = %v", err)
	}

	// Verify it's gone
	exists, _ = repo.Exists(ctx, "to-remove")
	if exists {
		t.Error("Project should not exist after unregister")
	}
}

func TestProjectRepository_UnregisterNonexistent(t *testing.T) {
	repo := NewProjectRepository()
	ctx := context.Background()

	// Unregistering non-existent project should not error
	if err := repo.Unregister(ctx, "nonexistent"); err != nil {
		t.Errorf("Unregister(nonexistent) error = %v, want nil", err)
	}
}

func TestProjectRepository_SetStatus(t *testing.T) {
	repo := NewProjectRepository()
	ctx := context.Background()

	project := &domain.Project{
		ID:     "status-test",
		Name:   "Status Test",
		Status: domain.ProjectStatusPending,
	}
	repo.Register(ctx, project)

	// Change status
	repo.SetStatus("status-test", domain.ProjectStatusRunning)

	// Verify
	retrieved, _ := repo.Get(ctx, "status-test")
	if retrieved.Status != domain.ProjectStatusRunning {
		t.Errorf("Status = %q, want %q", retrieved.Status, domain.ProjectStatusRunning)
	}

	// Change to error
	repo.SetStatus("status-test", domain.ProjectStatusError)
	retrieved, _ = repo.Get(ctx, "status-test")
	if retrieved.Status != domain.ProjectStatusError {
		t.Errorf("Status = %q, want %q", retrieved.Status, domain.ProjectStatusError)
	}
}

func TestProjectRepository_SetStatusNonexistent(t *testing.T) {
	repo := NewProjectRepository()

	// Should not panic on nonexistent project
	repo.SetStatus("nonexistent", domain.ProjectStatusRunning)
	// No error expected, just a no-op
}

func TestProjectRepository_RefreshStatus(t *testing.T) {
	repo := NewProjectRepository()
	ctx := context.Background()

	// RefreshStatus is a no-op for memory implementation
	if err := repo.RefreshStatus(ctx); err != nil {
		t.Errorf("RefreshStatus() error = %v, want nil", err)
	}
}

func TestProjectRepository_RegisterOverwrite(t *testing.T) {
	repo := NewProjectRepository()
	ctx := context.Background()

	// Register initial
	p1 := &domain.Project{
		ID:     "overwrite-test",
		Name:   "Original",
		Status: domain.ProjectStatusPending,
	}
	repo.Register(ctx, p1)

	// Register with same ID, different data
	p2 := &domain.Project{
		ID:     "overwrite-test",
		Name:   "Updated",
		Status: domain.ProjectStatusRunning,
	}
	repo.Register(ctx, p2)

	// Should have updated data
	retrieved, _ := repo.Get(ctx, "overwrite-test")
	if retrieved.Name != "Updated" {
		t.Errorf("Name = %q, want %q", retrieved.Name, "Updated")
	}
	if retrieved.Status != domain.ProjectStatusRunning {
		t.Errorf("Status = %q, want %q", retrieved.Status, domain.ProjectStatusRunning)
	}
}

func TestProjectRepository_ConcurrentAccess(t *testing.T) {
	repo := NewProjectRepository()
	ctx := context.Background()

	var wg sync.WaitGroup

	// Concurrent register
	for i := 0; i < 50; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			p := &domain.Project{
				ID:   domain.ProjectID(string(rune('a' + id%26))),
				Name: "Project",
			}
			repo.Register(ctx, p)
		}(i)
	}

	// Concurrent read
	for i := 0; i < 50; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			repo.List(ctx)
		}()
	}

	// Concurrent exists
	for i := 0; i < 50; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			_, _ = repo.Exists(ctx, domain.ProjectID(string(rune('a'+id%26))))
		}(i)
	}

	wg.Wait()
	// Test passes if no race/deadlock
}
