package domain

import (
	"errors"
	"testing"
)

func TestDomainError_Creation(t *testing.T) {
	tests := []struct {
		name     string
		code     ErrorCode
		message  string
		expected string
	}{
		{
			name:     "simple error",
			code:     ErrResourceNotFound,
			message:  "test resource not found",
			expected: "test resource not found",
		},
		{
			name:     "validation error",
			code:     ErrValidationFailed,
			message:  "invalid input",
			expected: "invalid input",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := NewDomainError(tt.code, tt.message)
			if err.Error() != tt.expected {
				t.Errorf("Expected error message %q, got %q", tt.expected, err.Error())
			}
			if err.Code != tt.code {
				t.Errorf("Expected error code %v, got %v", tt.code, err.Code)
			}
		})
	}
}

func TestDomainError_WithContext(t *testing.T) {
	err := NewDomainError(ErrResourceNotFound, "test error")
	err = err.WithContext("user_id", "123")
	err = err.WithContext("operation", "create")

	if len(err.Context) != 2 {
		t.Errorf("Expected 2 context items, got %d", len(err.Context))
	}

	if err.Context["user_id"] != "123" {
		t.Errorf("Expected user_id to be '123', got %v", err.Context["user_id"])
	}
}

func TestDomainError_WithDetails(t *testing.T) {
	err := NewDomainError(ErrValidationFailed, "validation failed")
	err = err.WithDetails("name field is required")

	expectedError := "validation failed: name field is required"
	if err.Error() != expectedError {
		t.Errorf("Expected error %q, got %q", expectedError, err.Error())
	}
}

func TestDomainError_WithCause(t *testing.T) {
	cause := errors.New("network timeout")
	err := NewDomainErrorWithCause(ErrNetworkError, "operation failed", cause)

	if err.Cause != cause {
		t.Error("Expected cause to be preserved")
	}

	if !errors.Is(err, cause) {
		t.Error("Expected error to wrap the cause")
	}
}

func TestAppError_Creation(t *testing.T) {
	appKey := AppKey{
		Organization: "test-org",
		Name:         "test-app",
		Version:      "1.0.0",
	}

	err := NewAppError(ErrResourceNotFound, "ShowApp", appKey, "US", "not found")

	expected := "operation ShowApp failed: resource app test-org/test-app version 1.0.0 in region US: not found"
	if err.Error() != expected {
		t.Errorf("Expected error %q, got %q", expected, err.Error())
	}

	if err.Resource.Type != "app" {
		t.Errorf("Expected resource type 'app', got %q", err.Resource.Type)
	}
}

func TestInstanceError_Creation(t *testing.T) {
	instanceKey := AppInstanceKey{
		Organization: "test-org",
		Name:         "test-instance",
		CloudletKey: CloudletKey{
			Organization: "cloudlet-org",
			Name:         "cloudlet-name",
		},
	}

	err := NewInstanceError(ErrResourceNotFound, "ShowAppInstance", instanceKey, "US", "not found")

	if err.Resource.Type != "app-instance" {
		t.Errorf("Expected resource type 'app-instance', got %q", err.Resource.Type)
	}

	if err.Operation != "ShowAppInstance" {
		t.Errorf("Expected operation 'ShowAppInstance', got %q", err.Operation)
	}
}

func TestErrorChecking_Functions(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		checkFn  func(error) bool
		expected bool
	}{
		{
			name:     "IsNotFoundError with not found error",
			err:      NewDomainError(ErrResourceNotFound, "not found"),
			checkFn:  IsNotFoundError,
			expected: true,
		},
		{
			name:     "IsNotFoundError with validation error",
			err:      NewDomainError(ErrValidationFailed, "invalid"),
			checkFn:  IsNotFoundError,
			expected: false,
		},
		{
			name:     "IsValidationError with validation error",
			err:      NewDomainError(ErrValidationFailed, "invalid"),
			checkFn:  IsValidationError,
			expected: true,
		},
		{
			name:     "IsRetryableError with network error",
			err:      NewDomainError(ErrNetworkError, "connection failed"),
			checkFn:  IsRetryableError,
			expected: true,
		},
		{
			name:     "IsRetryableError with validation error",
			err:      NewDomainError(ErrValidationFailed, "invalid"),
			checkFn:  IsRetryableError,
			expected: false,
		},
		{
			name:     "IsAuthenticationError with auth error",
			err:      NewDomainError(ErrAuthenticationFailed, "unauthorized"),
			checkFn:  IsAuthenticationError,
			expected: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.checkFn(tt.err)
			if result != tt.expected {
				t.Errorf("Expected %v, got %v", tt.expected, result)
			}
		})
	}
}

func TestErrorCode_String(t *testing.T) {
	tests := []struct {
		code     ErrorCode
		expected string
	}{
		{ErrResourceNotFound, "RESOURCE_NOT_FOUND"},
		{ErrValidationFailed, "VALIDATION_FAILED"},
		{ErrNetworkError, "NETWORK_ERROR"},
		{ErrUnknownError, "UNKNOWN_ERROR"},
	}

	for _, tt := range tests {
		t.Run(tt.expected, func(t *testing.T) {
			if tt.code.String() != tt.expected {
				t.Errorf("Expected %q, got %q", tt.expected, tt.code.String())
			}
		})
	}
}

func TestPredefinedErrors(t *testing.T) {
	// Test that predefined errors have correct codes
	if ErrAppNotFound.Code != ErrResourceNotFound {
		t.Error("ErrAppNotFound should have ErrResourceNotFound code")
	}

	if ErrInvalidAppKey.Code != ErrValidationFailed {
		t.Error("ErrInvalidAppKey should have ErrValidationFailed code")
	}

	if ErrDeploymentFailed.Code != ErrOperationNotAllowed {
		t.Error("ErrDeploymentFailed should have ErrOperationNotAllowed code")
	}
}