package errors

import (
	"errors"
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRetryableError(t *testing.T) {
	t.Run("Error message without retry delay", func(t *testing.T) {
		err := NewRetryableError(errors.New("connection failed"))
		assert.Equal(t, "retryable error: connection failed", err.Error())
	})

	t.Run("Error message with retry delay", func(t *testing.T) {
		err := NewRetryableErrorWithDelay(errors.New("rate limited"), 5*time.Second)
		assert.Equal(t, "retryable error (retry after 5s): rate limited", err.Error())
	})

	t.Run("Unwrap returns underlying error", func(t *testing.T) {
		underlying := errors.New("underlying error")
		err := NewRetryableError(underlying)
		assert.Equal(t, underlying, err.Unwrap())
	})

	t.Run("errors.Is works with wrapped error", func(t *testing.T) {
		err := NewRetryableError(ErrTimeout)
		assert.True(t, errors.Is(err, ErrTimeout))
	})
}

func TestProviderError(t *testing.T) {
	t.Run("Error message format", func(t *testing.T) {
		err := NewProviderError("AWS", "AddPermission", errors.New("access denied"))
		assert.Equal(t, "AWS: AddPermission: access denied", err.Error())
	})

	t.Run("Error message with nil underlying error", func(t *testing.T) {
		err := &ProviderError{Provider: "GCP", Op: "RemovePermission", Err: nil}
		assert.Equal(t, "GCP: RemovePermission: unknown error", err.Error())
	})

	t.Run("Unwrap returns underlying error", func(t *testing.T) {
		underlying := errors.New("underlying error")
		err := NewProviderError("Azure", "GetUserID", underlying)
		assert.Equal(t, underlying, err.Unwrap())
	})

	t.Run("errors.Is works with wrapped error", func(t *testing.T) {
		err := NewProviderError("Alibaba", "ValidateCredentials", ErrAuthFailed)
		assert.True(t, errors.Is(err, ErrAuthFailed))
	})
}

func TestIsRetryable(t *testing.T) {
	t.Run("Returns true for RetryableError", func(t *testing.T) {
		err := NewRetryableError(errors.New("temporary failure"))
		assert.True(t, IsRetryable(err))
	})

	t.Run("Returns true for wrapped RetryableError", func(t *testing.T) {
		retryable := NewRetryableError(errors.New("temporary failure"))
		wrapped := fmt.Errorf("operation failed: %w", retryable)
		assert.True(t, IsRetryable(wrapped))
	})

	t.Run("Returns false for non-retryable error", func(t *testing.T) {
		err := errors.New("permanent failure")
		assert.False(t, IsRetryable(err))
	})

	t.Run("Returns false for nil error", func(t *testing.T) {
		assert.False(t, IsRetryable(nil))
	})
}

func TestIsProviderError(t *testing.T) {
	t.Run("Returns true for ProviderError", func(t *testing.T) {
		err := NewProviderError("AWS", "AddPermission", errors.New("failed"))
		assert.True(t, IsProviderError(err))
	})

	t.Run("Returns true for wrapped ProviderError", func(t *testing.T) {
		providerErr := NewProviderError("GCP", "RemovePermission", errors.New("failed"))
		wrapped := fmt.Errorf("operation failed: %w", providerErr)
		assert.True(t, IsProviderError(wrapped))
	})

	t.Run("Returns false for non-provider error", func(t *testing.T) {
		err := errors.New("some error")
		assert.False(t, IsProviderError(err))
	})

	t.Run("Returns false for nil error", func(t *testing.T) {
		assert.False(t, IsProviderError(nil))
	})
}

func TestGetProviderError(t *testing.T) {
	t.Run("Extracts ProviderError", func(t *testing.T) {
		original := NewProviderError("AWS", "AddPermission", errors.New("failed"))
		wrapped := fmt.Errorf("operation failed: %w", original)

		extracted, ok := GetProviderError(wrapped)
		require.True(t, ok)
		assert.Equal(t, "AWS", extracted.Provider)
		assert.Equal(t, "AddPermission", extracted.Op)
	})

	t.Run("Returns false for non-provider error", func(t *testing.T) {
		err := errors.New("some error")
		_, ok := GetProviderError(err)
		assert.False(t, ok)
	})

	t.Run("Returns false for nil error", func(t *testing.T) {
		_, ok := GetProviderError(nil)
		assert.False(t, ok)
	})
}

func TestGetRetryableError(t *testing.T) {
	t.Run("Extracts RetryableError", func(t *testing.T) {
		original := NewRetryableErrorWithDelay(errors.New("rate limited"), 10*time.Second)
		wrapped := fmt.Errorf("operation failed: %w", original)

		extracted, ok := GetRetryableError(wrapped)
		require.True(t, ok)
		assert.Equal(t, 10*time.Second, extracted.RetryAfter)
	})

	t.Run("Returns false for non-retryable error", func(t *testing.T) {
		err := errors.New("some error")
		_, ok := GetRetryableError(err)
		assert.False(t, ok)
	})

	t.Run("Returns false for nil error", func(t *testing.T) {
		_, ok := GetRetryableError(nil)
		assert.False(t, ok)
	})
}

func TestIsConfigError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{"ErrConfigNotFound", ErrConfigNotFound, true},
		{"ErrConfigInvalid", ErrConfigInvalid, true},
		{"ErrConfigMissing", ErrConfigMissing, true},
		{"Wrapped ErrConfigNotFound", fmt.Errorf("failed: %w", ErrConfigNotFound), true},
		{"Non-config error", errors.New("other error"), false},
		{"Nil error", nil, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, IsConfigError(tt.err))
		})
	}
}

func TestIsAuthError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{"ErrAuthFailed", ErrAuthFailed, true},
		{"Wrapped ErrAuthFailed", fmt.Errorf("failed: %w", ErrAuthFailed), true},
		{"Non-auth error", errors.New("other error"), false},
		{"Nil error", nil, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, IsAuthError(tt.err))
		})
	}
}

func TestIsNotFoundError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{"ErrProviderNotFound", ErrProviderNotFound, true},
		{"ErrUserNotFound", ErrUserNotFound, true},
		{"ErrPermissionNotFound", ErrPermissionNotFound, true},
		{"Wrapped ErrUserNotFound", fmt.Errorf("failed: %w", ErrUserNotFound), true},
		{"Non-not-found error", errors.New("other error"), false},
		{"Nil error", nil, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, IsNotFoundError(tt.err))
		})
	}
}

func TestIsTemporaryError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{"ErrRateLimited", ErrRateLimited, true},
		{"ErrTimeout", ErrTimeout, true},
		{"RetryableError", NewRetryableError(errors.New("temp")), true},
		{"Wrapped ErrRateLimited", fmt.Errorf("failed: %w", ErrRateLimited), true},
		{"Non-temporary error", errors.New("permanent error"), false},
		{"Nil error", nil, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, IsTemporaryError(tt.err))
		})
	}
}

func TestIsCancellationError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{"ErrCanceled", ErrCanceled, true},
		{"Wrapped ErrCanceled", fmt.Errorf("failed: %w", ErrCanceled), true},
		{"Non-cancellation error", errors.New("other error"), false},
		{"Nil error", nil, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, IsCancellationError(tt.err))
		})
	}
}

func TestSentinelErrors(t *testing.T) {
	// Verify all sentinel errors are distinct
	sentinelErrors := []error{
		ErrConfigNotFound,
		ErrConfigInvalid,
		ErrConfigMissing,
		ErrProviderNotFound,
		ErrUserNotFound,
		ErrPermissionNotFound,
		ErrAuthFailed,
		ErrRateLimited,
		ErrTimeout,
		ErrCanceled,
	}

	for i, err1 := range sentinelErrors {
		for j, err2 := range sentinelErrors {
			if i != j {
				assert.False(t, errors.Is(err1, err2),
					"Expected %v and %v to be distinct errors", err1, err2)
			}
		}
	}
}

func TestErrorChaining(t *testing.T) {
	t.Run("ProviderError wrapping RetryableError", func(t *testing.T) {
		retryable := NewRetryableErrorWithDelay(ErrRateLimited, 5*time.Second)
		providerErr := NewProviderError("AWS", "AddPermission", retryable)

		// Should be able to detect both error types
		assert.True(t, IsProviderError(providerErr))
		assert.True(t, IsRetryable(providerErr))
		assert.True(t, errors.Is(providerErr, ErrRateLimited))
	})

	t.Run("Multiple levels of wrapping", func(t *testing.T) {
		base := ErrAuthFailed
		retryable := NewRetryableError(base)
		providerErr := NewProviderError("GCP", "ValidateCredentials", retryable)
		wrapped := fmt.Errorf("operation failed: %w", providerErr)

		assert.True(t, errors.Is(wrapped, ErrAuthFailed))
		assert.True(t, IsProviderError(wrapped))
		assert.True(t, IsRetryable(wrapped))
	})
}
