package jackbox

import (
	"errors"
	"fmt"
)

// Sentinel errors for common failure scenarios
var (
	// ErrNotAuthenticated indicates the client is not authenticated
	ErrNotAuthenticated = errors.New("not authenticated")

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

	// ErrConnectionLost indicates the WebSocket connection was lost
	ErrConnectionLost = errors.New("connection lost")

	// ErrTokenExpired indicates the authentication token has expired
	ErrTokenExpired = errors.New("token expired")

	// ErrInvalidResponse indicates an unexpected response from the API
	ErrInvalidResponse = errors.New("invalid response from API")

	// ErrSessionNotFound indicates the specified session does not exist
	ErrSessionNotFound = errors.New("session not found")

	// ErrNotSubscribed indicates not subscribed to any session
	ErrNotSubscribed = errors.New("not subscribed to any session")
)

// APIError represents an API-related error with context
type APIError struct {
	Op         string // Operation that failed (e.g., "vote", "get_session", "authenticate")
	StatusCode int    // HTTP status code
	Message    string // Error message from API
	Err        error  // Underlying error
}

func (e *APIError) Error() string {
	if e.StatusCode > 0 {
		return fmt.Sprintf("API error during %s (HTTP %d): %s", e.Op, e.StatusCode, e.Message)
	}
	if e.Err != nil {
		return fmt.Sprintf("API error during %s: %s (%v)", e.Op, e.Message, e.Err)
	}
	return fmt.Sprintf("API error during %s: %s", e.Op, e.Message)
}

func (e *APIError) Unwrap() error {
	return e.Err
}

// WebSocketError represents a WebSocket-related error with context
type WebSocketError struct {
	Op  string // Operation that failed (e.g., "connect", "subscribe", "send")
	Err error  // Underlying error
}

func (e *WebSocketError) Error() string {
	return fmt.Sprintf("WebSocket error during %s: %v", e.Op, e.Err)
}

func (e *WebSocketError) Unwrap() error {
	return e.Err
}

// SessionError represents a session-related error with context
type SessionError struct {
	Op        string // Operation that failed (e.g., "subscribe", "get_active")
	SessionID int    // Session ID
	Err       error  // Underlying error
}

func (e *SessionError) Error() string {
	return fmt.Sprintf("session error during %s for session %d: %v", e.Op, e.SessionID, e.Err)
}

func (e *SessionError) Unwrap() error {
	return e.Err
}

// IsRetryable returns true if the error is transient and the operation should be retried
func IsRetryable(err error) bool {
	if err == nil {
		return false
	}

	// Check for known retryable errors
	if errors.Is(err, ErrConnectionLost) ||
		errors.Is(err, ErrTokenExpired) {
		return true
	}

	// Check for API errors with retryable status codes
	var apiErr *APIError
	if errors.As(err, &apiErr) {
		// 5xx errors are typically retryable
		if apiErr.StatusCode >= 500 && apiErr.StatusCode < 600 {
			return true
		}
		// 429 Too Many Requests is retryable
		if apiErr.StatusCode == 429 {
			return true
		}
	}

	// WebSocket errors are generally retryable
	var wsErr *WebSocketError
	if errors.As(err, &wsErr) {
		return true
	}

	return false
}

// IsFatal returns true if the error is fatal and reconnection should not be attempted
func IsFatal(err error) bool {
	if err == nil {
		return false
	}

	// Check for known fatal errors
	if errors.Is(err, ErrAuthFailed) ||
		errors.Is(err, ErrSessionNotFound) {
		return true
	}

	// Check for API errors with fatal status codes
	var apiErr *APIError
	if errors.As(err, &apiErr) {
		// 4xx errors (except 429) are typically fatal
		if apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 && apiErr.StatusCode != 429 {
			return true
		}
	}

	return false
}
