package errors

import (
	"fmt"
	"runtime"
	"strings"

	"github.com/pkg/errors"
)

// Error es una interfaz extendida para errores
type Error interface {
	error
	Code() string
	Unwrap() error
}

// CustomError implementa la interfaz Error
type CustomError struct {
	code    string
	message string
	cause   error
	stack   []uintptr
}

// New crea un nuevo error personalizado
func New(code, message string) Error {
	return &CustomError{
		code:    code,
		message: message,
		stack:   callers(),
	}
}

// Wrap envuelve un error con información adicional
func Wrap(err error, message string) Error {
	if err == nil {
		return nil
	}

	return &CustomError{
		code:    extractCode(err),
		message: message,
		cause:   err,
		stack:   callers(),
	}
}

// Wrapf envuelve un error con un mensaje formateado
func Wrapf(err error, format string, args ...interface{}) Error {
	if err == nil {
		return nil
	}

	return &CustomError{
		code:    extractCode(err),
		message: fmt.Sprintf(format, args...),
		cause:   err,
		stack:   callers(),
	}
}

// NewWithCode crea un nuevo error con un código específico
func NewWithCode(code, message string) Error {
	return &CustomError{
		code:    code,
		message: message,
		stack:   callers(),
	}
}

// Error implementa la interfaz error
func (e *CustomError) Error() string {
	if e.cause != nil {
		return fmt.Sprintf("%s: %v", e.message, e.cause)
	}
	return e.message
}

// Code retorna el código de error
func (e *CustomError) Code() string {
	return e.code
}

// Unwrap implementa la interfaz de unwrapping
func (e *CustomError) Unwrap() error {
	return e.cause
}

// Stack retorna la pila de llamadas
func (e *CustomError) Stack() string {
	var sb strings.Builder
	frames := runtime.CallersFrames(e.stack)
	for {
		frame, more := frames.Next()
		sb.WriteString(fmt.Sprintf("%s:%d %s\n", frame.File, frame.Line, frame.Function))
		if !more {
			break
		}
	}
	return sb.String()
}

// extractCode extrae el código de un error
func extractCode(err error) string {
	if customErr, ok := err.(Error); ok {
		return customErr.Code()
	}
	return "UNKNOWN"
}

// callers obtiene la pila de llamadas
func callers() []uintptr {
	const depth = 32
	var pcs [depth]uintptr
	n := runtime.Callers(3, pcs[:])
	return pcs[0:n]
}

// Errores comunes
var (
	ErrNotFound       = NewWithCode("NOT_FOUND", "recurso no encontrado")
	ErrUnauthorized   = NewWithCode("UNAUTHORIZED", "no autorizado")
	ErrForbidden      = NewWithCode("FORBIDDEN", "acceso prohibido")
	ErrBadRequest     = NewWithCode("BAD_REQUEST", "solicitud incorrecta")
	ErrInternal       = NewWithCode("INTERNAL", "error interno del servidor")
	ErrTimeout        = NewWithCode("TIMEOUT", "tiempo de espera agotado")
	ErrNotImplemented = NewWithCode("NOT_IMPLEMENTED", "no implementado")
)
