package cqlerrors

import "errors"

// RetryKind classifies whether a backend failure that was mapped to a CQL error
// is safe for the server to transparently retry, and how. The zero value means
// "not retryable".
type RetryKind int

const (
	// RetryNone marks an error the server must not retry.
	RetryNone RetryKind = iota
	// RetryDeadlock marks a deadlock (MySQL 1213): the transaction was fully
	// rolled back, so re-running the whole unit is safe.
	RetryDeadlock
)

// CQL error codes used by the protocol layer.
const (
	CodeProtocol      = 0x000A
	CodeUnavailable   = 0x1000
	CodeOverloaded    = 0x1001
	CodeWriteTimeout  = 0x1100
	CodeReadTimeout   = 0x1200
	CodeInvalid       = 0x2200
	CodeAlreadyExists = 0x2300
	CodeUnauthorized  = 0x2400
	CodeUnprepared    = 0x2500
)

// Error wraps a CQL error code with a message.
type Error struct {
	code  int
	msg   string
	retry RetryKind
}

func (e *Error) Error() string {
	return e.msg
}

// Code returns the protocol error code for this error.
func (e *Error) Code() int {
	return e.code
}

// RetryKind reports how the server may retry this error (RetryNone if not).
func (e *Error) RetryKind() RetryKind {
	return e.retry
}

// RetryKindOf extracts the RetryKind carried by err (seeing through wrapping),
// returning RetryNone when err is nil or carries no retry classification.
func RetryKindOf(err error) RetryKind {
	if err == nil {
		return RetryNone
	}
	var classifier interface{ RetryKind() RetryKind }
	if errors.As(err, &classifier) {
		return classifier.RetryKind()
	}
	return RetryNone
}

// Protocol creates a Protocol error with the provided message.
func Protocol(msg string) error {
	return &Error{code: CodeProtocol, msg: msg}
}

// Unavailable creates an Unavailable error with the provided message.
func Unavailable(msg string) error {
	return &Error{code: CodeUnavailable, msg: msg}
}

// Overloaded creates an Overloaded error with the provided message.
func Overloaded(msg string) error {
	return &Error{code: CodeOverloaded, msg: msg}
}

// WriteTimeout creates a WriteTimeout error with the provided message.
func WriteTimeout(msg string) error {
	return &Error{code: CodeWriteTimeout, msg: msg}
}

// WriteTimeoutRetryable creates a WriteTimeout error tagged with a RetryKind so
// the server can decide whether to transparently retry it. The wire encoding is
// identical to WriteTimeout; the kind is server-side metadata only.
func WriteTimeoutRetryable(msg string, kind RetryKind) error {
	return &Error{code: CodeWriteTimeout, msg: msg, retry: kind}
}

// ReadTimeout creates a ReadTimeout error with the provided message.
func ReadTimeout(msg string) error {
	return &Error{code: CodeReadTimeout, msg: msg}
}

// Invalid creates an Invalid error with the provided message.
func Invalid(msg string) error {
	return &Error{code: CodeInvalid, msg: msg}
}

// AlreadyExists creates an AlreadyExists error with the provided message.
func AlreadyExists(msg string) error {
	return &Error{code: CodeAlreadyExists, msg: msg}
}

// Unauthorized creates an Unauthorized error with the provided message.
func Unauthorized(msg string) error {
	return &Error{code: CodeUnauthorized, msg: msg}
}

// Unprepared creates an Unprepared error with the provided message.
func Unprepared(msg string) error {
	return &Error{code: CodeUnprepared, msg: msg}
}
