package bkosmi

import (
	"errors"
	"fmt"
)

// Sentinel errors for common failure scenarios
var (
	// ErrNotConnected indicates the WebSocket connection is not established
	ErrNotConnected = errors.New("not connected to Kosmi")

	// ErrAuthFailed indicates authentication with Kosmi 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")

	// ErrInvalidRoomID indicates the room ID format is invalid
	ErrInvalidRoomID = errors.New("invalid room ID")

	// ErrRoomNotFound indicates the specified room does not exist
	ErrRoomNotFound = errors.New("room not found")

	// ErrMessageSendFailed indicates a message could not be sent
	ErrMessageSendFailed = errors.New("failed to send message")

	// ErrSubscriptionFailed indicates subscription to room messages failed
	ErrSubscriptionFailed = errors.New("failed to subscribe to messages")

	// ErrJoinRoomFailed indicates joining the room failed
	ErrJoinRoomFailed = errors.New("failed to join room")

	// ErrConnectionTimeout indicates a connection timeout occurred
	ErrConnectionTimeout = errors.New("connection timeout")

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

// ConnectionError represents a connection-related error with context
type ConnectionError struct {
	Op  string // Operation that failed (e.g., "dial", "handshake", "subscribe")
	URL string // WebSocket URL
	Err error  // Underlying error
}

func (e *ConnectionError) Error() string {
	return fmt.Sprintf("connection error during %s to %s: %v", e.Op, e.URL, e.Err)
}

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

// AuthError represents an authentication-related error with context
type AuthError struct {
	Op     string // Operation that failed (e.g., "login", "token_refresh", "anonymous_login")
	Reason string // Human-readable reason
	Err    error  // Underlying error
}

func (e *AuthError) Error() string {
	if e.Err != nil {
		return fmt.Sprintf("auth error during %s: %s (%v)", e.Op, e.Reason, e.Err)
	}
	return fmt.Sprintf("auth error during %s: %s", e.Op, e.Reason)
}

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

// MessageError represents a message-related error with context
type MessageError struct {
	Op      string // Operation that failed (e.g., "send", "receive", "parse")
	RoomID  string // Room ID
	Message string // Message content (truncated if long)
	Err     error  // Underlying error
}

func (e *MessageError) Error() string {
	return fmt.Sprintf("message error during %s in room %s: %v", e.Op, e.RoomID, e.Err)
}

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

// RoomError represents a room-related error with context
type RoomError struct {
	Op     string // Operation that failed (e.g., "join", "leave", "subscribe")
	RoomID string // Room ID
	Err    error  // Underlying error
}

func (e *RoomError) Error() string {
	return fmt.Sprintf("room error during %s for room %s: %v", e.Op, e.RoomID, e.Err)
}

func (e *RoomError) 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, ErrConnectionTimeout) ||
		errors.Is(err, ErrTokenExpired) {
		return true
	}

	// Check for connection errors (usually retryable)
	var connErr *ConnectionError
	if errors.As(err, &connErr) {
		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, ErrInvalidRoomID) ||
		errors.Is(err, ErrRoomNotFound) {
		return true
	}

	// Check for auth errors with specific reasons
	var authErr *AuthError
	if errors.As(err, &authErr) {
		// Invalid credentials are fatal
		if authErr.Reason == "invalid credentials" ||
			authErr.Reason == "account not found" {
			return true
		}
	}

	return false
}

