package domain_test

import (
	"errors"
	"testing"
	"time"

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

// =============================================================================
// CommandResult Tests
// =============================================================================

func TestCommandResult_Success(t *testing.T) {
	tests := []struct {
		name     string
		exitCode int
		err      error
		want     bool
	}{
		{
			name:     "success with zero exit code and no error",
			exitCode: 0,
			err:      nil,
			want:     true,
		},
		{
			name:     "failure with non-zero exit code",
			exitCode: 1,
			err:      nil,
			want:     false,
		},
		{
			name:     "failure with error",
			exitCode: 0,
			err:      errors.New("execution failed"),
			want:     false,
		},
		{
			name:     "failure with both error and non-zero exit",
			exitCode: 127,
			err:      errors.New("command not found"),
			want:     false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := &domain.CommandResult{
				ExitCode: tt.exitCode,
				Error:    tt.err,
			}
			if got := result.Success(); got != tt.want {
				t.Errorf("Success() = %v, want %v", got, tt.want)
			}
		})
	}
}

// =============================================================================
// ProjectStatus Tests
// =============================================================================

func TestProjectStatus_IsAvailable(t *testing.T) {
	tests := []struct {
		status domain.ProjectStatus
		want   bool
	}{
		{domain.ProjectStatusRunning, true},
		{domain.ProjectStatusPending, false},
		{domain.ProjectStatusFailed, false},
		{domain.ProjectStatusNotFound, false},
		{domain.ProjectStatusUnknown, false},
		{domain.ProjectStatusError, false},
	}

	for _, tt := range tests {
		t.Run(string(tt.status), func(t *testing.T) {
			if got := tt.status.IsAvailable(); got != tt.want {
				t.Errorf("ProjectStatus(%q).IsAvailable() = %v, want %v", tt.status, got, tt.want)
			}
		})
	}
}

func TestProjectStatus_IsTerminal(t *testing.T) {
	tests := []struct {
		status domain.ProjectStatus
		want   bool
	}{
		{domain.ProjectStatusRunning, false},
		{domain.ProjectStatusPending, false},
		{domain.ProjectStatusFailed, true},
		{domain.ProjectStatusNotFound, true},
		{domain.ProjectStatusUnknown, false},
		{domain.ProjectStatusError, false},
	}

	for _, tt := range tests {
		t.Run(string(tt.status), func(t *testing.T) {
			if got := tt.status.IsTerminal(); got != tt.want {
				t.Errorf("ProjectStatus(%q).IsTerminal() = %v, want %v", tt.status, got, tt.want)
			}
		})
	}
}

// =============================================================================
// Error Variables Tests
// =============================================================================

func TestErrorVariables_AreDistinct(t *testing.T) {
	// Verify all domain errors are distinct and can be matched with errors.Is
	allErrors := []error{
		domain.ErrProjectNotFound,
		domain.ErrProjectNotRunning,
		domain.ErrCommandNotFound,
		domain.ErrCommandTimeout,
		domain.ErrCommandCancelled,
		domain.ErrLimitExceeded,
		domain.ErrInvalidCommand,
		domain.ErrCommandSanitization,
		domain.ErrKeyNotFound,
		domain.ErrKeyRevoked,
		domain.ErrKeyExpired,
		domain.ErrKeyInvalid,
		domain.ErrUnauthorized,
		domain.ErrForbidden,
		domain.ErrInsufficientScope,
		domain.ErrRateLimited,
		domain.ErrDatabaseConnection,
		domain.ErrKubernetesError,
	}

	// Each error should only match itself
	for i, err1 := range allErrors {
		for j, err2 := range allErrors {
			if i == j {
				if !errors.Is(err1, err2) {
					t.Errorf("error %v should match itself", err1)
				}
			} else {
				if errors.Is(err1, err2) {
					t.Errorf("error %v should not match %v", err1, err2)
				}
			}
		}
	}
}

func TestErrorVariables_CanBeWrapped(t *testing.T) {
	// Domain errors should be usable as base errors for wrapping
	wrapped := errors.Join(domain.ErrProjectNotFound, errors.New("additional context"))

	if !errors.Is(wrapped, domain.ErrProjectNotFound) {
		t.Error("wrapped error should match base domain error")
	}
}

// =============================================================================
// Type Constants Tests
// =============================================================================

func TestScopeConstants(t *testing.T) {
	// Verify scope constants have expected values (for documentation)
	expectedScopes := map[domain.Scope]string{
		domain.ScopeAdmin:           "admin",
		domain.ScopeProjectsRead:    "projects:read",
		domain.ScopeProjectsExecute: "projects:execute",
		domain.ScopeKeysRead:        "keys:read",
		domain.ScopeKeysWrite:       "keys:write",
		domain.ScopeAuditRead:       "audit:read",
		domain.ScopeQueueRead:       "queue:read",
		domain.ScopeQueueWrite:      "queue:write",
		domain.ScopeWebhookRead:     "webhook:read",
		domain.ScopeWebhookWrite:    "webhook:write",
	}

	for scope, expected := range expectedScopes {
		if string(scope) != expected {
			t.Errorf("Scope %v = %q, want %q", scope, string(scope), expected)
		}
	}
}

func TestCommandTypeConstants(t *testing.T) {
	expectedTypes := map[domain.CommandType]string{
		domain.CommandTypeClaude: "claude",
		domain.CommandTypeShell:  "shell",
		domain.CommandTypeGit:    "git",
	}

	for cmdType, expected := range expectedTypes {
		if string(cmdType) != expected {
			t.Errorf("CommandType %v = %q, want %q", cmdType, string(cmdType), expected)
		}
	}
}

func TestProjectStatusConstants(t *testing.T) {
	expectedStatuses := map[domain.ProjectStatus]string{
		domain.ProjectStatusRunning:  "running",
		domain.ProjectStatusPending:  "pending",
		domain.ProjectStatusFailed:   "failed",
		domain.ProjectStatusNotFound: "not_found",
		domain.ProjectStatusUnknown:  "unknown",
		domain.ProjectStatusError:    "error",
	}

	for status, expected := range expectedStatuses {
		if string(status) != expected {
			t.Errorf("ProjectStatus %v = %q, want %q", status, string(status), expected)
		}
	}
}

// =============================================================================
// Type Instantiation Tests
// =============================================================================

func TestProject_CanBeInstantiated(t *testing.T) {
	proj := domain.Project{
		ID:          "test-project",
		Name:        "Test Project",
		Description: "A test project",
		PodName:     "test-pod",
		Status:      domain.ProjectStatusRunning,
		Workspace:   "/workspace",
	}

	if proj.ID != "test-project" {
		t.Errorf("Project.ID = %q, want %q", proj.ID, "test-project")
	}
}

func TestCommand_CanBeInstantiated(t *testing.T) {
	now := time.Now()
	cmd := domain.Command{
		ID:        "cmd-1",
		ProjectID: "proj-1",
		Type:      domain.CommandTypeClaude,
		Args:      []string{"--version"},
		StartedAt: now,
	}

	if cmd.ID != "cmd-1" {
		t.Errorf("Command.ID = %q, want %q", cmd.ID, "cmd-1")
	}
	if len(cmd.Args) != 1 {
		t.Errorf("Command.Args length = %d, want 1", len(cmd.Args))
	}
}

func TestOutputLine_CanBeInstantiated(t *testing.T) {
	now := time.Now()
	line := domain.OutputLine{
		Stream:    "stdout",
		Line:      "Hello, world!",
		Timestamp: now,
	}

	if line.Stream != "stdout" {
		t.Errorf("OutputLine.Stream = %q, want %q", line.Stream, "stdout")
	}
}
