// Package errors provides common error types used throughout the AutoSSO application.
package errors

import (
	"errors"
	"fmt"
	"time"
)

// Common error types for the application.
var (
	// ErrConfigNotFound indicates that the configuration file was not found.
	ErrConfigNotFound = errors.New("configuration file not found")

	// ErrConfigInvalid indicates that the configuration is invalid.
	ErrConfigInvalid = errors.New("configuration is invalid")

	// ErrConfigMissing indicates that a required configuration is missing.
	ErrConfigMissing = errors.New("required configuration is missing")

	// ErrProviderNotFound indicates that the cloud provider was not found.
	ErrProviderNotFound = errors.New("cloud provider not found")

	// ErrUserNotFound indicates that the user was not found.
	ErrUserNotFound = errors.New("user not found")

	// ErrPermissionNotFound indicates that the permission set was not found.
	ErrPermissionNotFound = errors.New("permission set not found")

	// ErrAuthFailed indicates that authentication failed.
	ErrAuthFailed = errors.New("authentication failed")

	// ErrRateLimited indicates that the operation was rate limited.
	ErrRateLimited = errors.New("rate limited")

	// ErrTimeout indicates that the operation timed out.
	ErrTimeout = errors.New("operation timed out")

	// ErrCanceled indicates that the operation was canceled.
	ErrCanceled = errors.New("operation canceled")
)

// RetryableError represents an error that can be retried.
// It wraps an underlying error and optionally specifies a duration to wait before retrying.
type RetryableError struct {
	// Err is the underlying error.
	Err error
	// RetryAfter is the suggested duration to wait before retrying.
	// A zero value indicates no specific retry delay is suggested.
	RetryAfter time.Duration
}

// Error returns the error message.
func (e *RetryableError) Error() string {
	if e.RetryAfter > 0 {
		return fmt.Sprintf("retryable error (retry after %v): %v", e.RetryAfter, e.Err)
	}
	return fmt.Sprintf("retryable error: %v", e.Err)
}

// Unwrap returns the underlying error.
func (e *RetryableError) Unwrap() error {
	return e.Err
}

// NewRetryableError creates a new RetryableError with the given underlying error.
func NewRetryableError(err error) *RetryableError {
	return &RetryableError{Err: err}
}

// NewRetryableErrorWithDelay creates a new RetryableError with a suggested retry delay.
func NewRetryableErrorWithDelay(err error, retryAfter time.Duration) *RetryableError {
	return &RetryableError{Err: err, RetryAfter: retryAfter}
}

// ProviderError represents an error that occurred during a cloud provider operation.
// It includes the provider name, operation name, and the underlying error.
type ProviderError struct {
	// Provider is the name of the cloud provider (e.g., "AWS", "GCP", "Azure", "Alibaba").
	Provider string
	// Op is the name of the operation that failed (e.g., "AddPermission", "RemovePermission").
	Op string
	// Err is the underlying error.
	Err error
}

// Error returns the error message.
func (e *ProviderError) Error() string {
	if e.Err != nil {
		return fmt.Sprintf("%s: %s: %v", e.Provider, e.Op, e.Err)
	}
	return fmt.Sprintf("%s: %s: unknown error", e.Provider, e.Op)
}

// Unwrap returns the underlying error.
func (e *ProviderError) Unwrap() error {
	return e.Err
}

// NewProviderError creates a new ProviderError.
func NewProviderError(provider, op string, err error) *ProviderError {
	return &ProviderError{
		Provider: provider,
		Op:       op,
		Err:      err,
	}
}

// IsRetryable checks if an error is retryable.
// It returns true if the error is a RetryableError or wraps one.
func IsRetryable(err error) bool {
	if err == nil {
		return false
	}

	var retryableErr *RetryableError
	return errors.As(err, &retryableErr)
}

// IsProviderError checks if an error is a ProviderError.
// It returns true if the error is a ProviderError or wraps one.
func IsProviderError(err error) bool {
	if err == nil {
		return false
	}

	var providerErr *ProviderError
	return errors.As(err, &providerErr)
}

// GetProviderError extracts the ProviderError from an error if present.
func GetProviderError(err error) (*ProviderError, bool) {
	if err == nil {
		return nil, false
	}

	var providerErr *ProviderError
	if errors.As(err, &providerErr) {
		return providerErr, true
	}
	return nil, false
}

// GetRetryableError extracts the RetryableError from an error if present.
func GetRetryableError(err error) (*RetryableError, bool) {
	if err == nil {
		return nil, false
	}

	var retryableErr *RetryableError
	if errors.As(err, &retryableErr) {
		return retryableErr, true
	}
	return nil, false
}

// IsConfigError checks if an error is a configuration-related error.
func IsConfigError(err error) bool {
	return errors.Is(err, ErrConfigNotFound) ||
		errors.Is(err, ErrConfigInvalid) ||
		errors.Is(err, ErrConfigMissing)
}

// IsAuthError checks if an error is an authentication-related error.
func IsAuthError(err error) bool {
	return errors.Is(err, ErrAuthFailed)
}

// IsNotFoundError checks if an error is a not-found error.
func IsNotFoundError(err error) bool {
	return errors.Is(err, ErrProviderNotFound) ||
		errors.Is(err, ErrUserNotFound) ||
		errors.Is(err, ErrPermissionNotFound)
}

// IsTemporaryError checks if an error is a temporary error that might succeed on retry.
// This includes rate limiting, timeouts, and explicitly retryable errors.
func IsTemporaryError(err error) bool {
	return errors.Is(err, ErrRateLimited) ||
		errors.Is(err, ErrTimeout) ||
		IsRetryable(err)
}

// IsCancellationError checks if an error indicates the operation was canceled.
func IsCancellationError(err error) bool {
	return errors.Is(err, ErrCanceled)
}
