package errors

import (
	"fmt"
	"net/http"
)

// ErrorCode represents different types of errors in the system
type ErrorCode string

const (
	// Authentication and Authorization errors
	ErrUnauthorized     ErrorCode = "UNAUTHORIZED"
	ErrForbidden        ErrorCode = "FORBIDDEN"
	ErrInvalidToken     ErrorCode = "INVALID_TOKEN"
	ErrTokenExpired     ErrorCode = "TOKEN_EXPIRED"
	ErrInvalidCredentials ErrorCode = "INVALID_CREDENTIALS"

	// Validation errors
	ErrValidationFailed ErrorCode = "VALIDATION_FAILED"
	ErrInvalidInput     ErrorCode = "INVALID_INPUT"
	ErrMissingField     ErrorCode = "MISSING_FIELD"
	ErrInvalidFormat    ErrorCode = "INVALID_FORMAT"

	// Resource errors
	ErrNotFound         ErrorCode = "NOT_FOUND"
	ErrAlreadyExists    ErrorCode = "ALREADY_EXISTS"
	ErrConflict         ErrorCode = "CONFLICT"

	// System errors
	ErrInternal         ErrorCode = "INTERNAL_ERROR"
	ErrDatabase         ErrorCode = "DATABASE_ERROR"
	ErrExternal         ErrorCode = "EXTERNAL_SERVICE_ERROR"
	ErrTimeout          ErrorCode = "TIMEOUT"
	ErrRateLimit        ErrorCode = "RATE_LIMIT_EXCEEDED"

	// Business logic errors
	ErrInsufficientPermissions ErrorCode = "INSUFFICIENT_PERMISSIONS"
	ErrApplicationNotFound     ErrorCode = "APPLICATION_NOT_FOUND"
	ErrTokenNotFound          ErrorCode = "TOKEN_NOT_FOUND"
	ErrPermissionNotFound     ErrorCode = "PERMISSION_NOT_FOUND"
	ErrInvalidApplication     ErrorCode = "INVALID_APPLICATION"
	ErrTokenCreationFailed    ErrorCode = "TOKEN_CREATION_FAILED"
)

// AppError represents an application error with context
type AppError struct {
	Code       ErrorCode              `json:"code"`
	Message    string                 `json:"message"`
	Details    string                 `json:"details,omitempty"`
	StatusCode int                    `json:"-"`
	Internal   error                  `json:"-"`
	Context    map[string]interface{} `json:"context,omitempty"`
}

// Error implements the error interface
func (e *AppError) Error() string {
	if e.Internal != nil {
		return fmt.Sprintf("%s: %s (internal: %v)", e.Code, e.Message, e.Internal)
	}
	return fmt.Sprintf("%s: %s", e.Code, e.Message)
}

// WithContext adds context information to the error
func (e *AppError) WithContext(key string, value interface{}) *AppError {
	if e.Context == nil {
		e.Context = make(map[string]interface{})
	}
	e.Context[key] = value
	return e
}

// WithDetails adds additional details to the error
func (e *AppError) WithDetails(details string) *AppError {
	e.Details = details
	return e
}

// WithInternal adds the underlying error
func (e *AppError) WithInternal(err error) *AppError {
	e.Internal = err
	return e
}

// New creates a new application error
func New(code ErrorCode, message string) *AppError {
	return &AppError{
		Code:       code,
		Message:    message,
		StatusCode: getHTTPStatusCode(code),
	}
}

// Wrap wraps an existing error with application error context
func Wrap(err error, code ErrorCode, message string) *AppError {
	return &AppError{
		Code:       code,
		Message:    message,
		StatusCode: getHTTPStatusCode(code),
		Internal:   err,
	}
}

// getHTTPStatusCode maps error codes to HTTP status codes
func getHTTPStatusCode(code ErrorCode) int {
	switch code {
	case ErrUnauthorized, ErrInvalidToken, ErrTokenExpired, ErrInvalidCredentials:
		return http.StatusUnauthorized
	case ErrForbidden, ErrInsufficientPermissions:
		return http.StatusForbidden
	case ErrValidationFailed, ErrInvalidInput, ErrMissingField, ErrInvalidFormat:
		return http.StatusBadRequest
	case ErrNotFound, ErrApplicationNotFound, ErrTokenNotFound, ErrPermissionNotFound:
		return http.StatusNotFound
	case ErrAlreadyExists, ErrConflict:
		return http.StatusConflict
	case ErrRateLimit:
		return http.StatusTooManyRequests
	case ErrTimeout:
		return http.StatusRequestTimeout
	case ErrInternal, ErrDatabase, ErrExternal, ErrTokenCreationFailed:
		return http.StatusInternalServerError
	default:
		return http.StatusInternalServerError
	}
}

// IsRetryable determines if an error is retryable
func (e *AppError) IsRetryable() bool {
	switch e.Code {
	case ErrTimeout, ErrExternal, ErrDatabase:
		return true
	default:
		return false
	}
}

// IsClientError determines if an error is a client error (4xx)
func (e *AppError) IsClientError() bool {
	return e.StatusCode >= 400 && e.StatusCode < 500
}

// IsServerError determines if an error is a server error (5xx)
func (e *AppError) IsServerError() bool {
	return e.StatusCode >= 500
}

// Common error constructors for frequently used errors

// NewUnauthorizedError creates an unauthorized error
func NewUnauthorizedError(message string) *AppError {
	return New(ErrUnauthorized, message)
}

// NewForbiddenError creates a forbidden error
func NewForbiddenError(message string) *AppError {
	return New(ErrForbidden, message)
}

// NewValidationError creates a validation error
func NewValidationError(message string) *AppError {
	return New(ErrValidationFailed, message)
}

// NewNotFoundError creates a not found error
func NewNotFoundError(resource string) *AppError {
	return New(ErrNotFound, fmt.Sprintf("%s not found", resource))
}

// NewAlreadyExistsError creates an already exists error
func NewAlreadyExistsError(resource string) *AppError {
	return New(ErrAlreadyExists, fmt.Sprintf("%s already exists", resource))
}

// NewInternalError creates an internal server error
func NewInternalError(message string) *AppError {
	return New(ErrInternal, message)
}

// NewDatabaseError creates a database error
func NewDatabaseError(operation string, err error) *AppError {
	return Wrap(err, ErrDatabase, fmt.Sprintf("Database operation failed: %s", operation))
}

// NewTokenError creates a token-related error
func NewTokenError(message string) *AppError {
	return New(ErrInvalidToken, message)
}

// NewApplicationError creates an application-related error
func NewApplicationError(message string) *AppError {
	return New(ErrInvalidApplication, message)
}

// NewPermissionError creates a permission-related error
func NewPermissionError(message string) *AppError {
	return New(ErrInsufficientPermissions, message)
}

// NewAuthenticationError creates an authentication error
func NewAuthenticationError(message string) *AppError {
	return New(ErrUnauthorized, message)
}

// NewConfigurationError creates a configuration error
func NewConfigurationError(message string) *AppError {
	return New(ErrInternal, message)
}

// ErrorResponse represents the JSON error response format
type ErrorResponse struct {
	Error   string                 `json:"error"`
	Message string                 `json:"message"`
	Code    ErrorCode              `json:"code"`
	Details string                 `json:"details,omitempty"`
	Context map[string]interface{} `json:"context,omitempty"`
}

// ToResponse converts an AppError to an ErrorResponse
func (e *AppError) ToResponse() ErrorResponse {
	return ErrorResponse{
		Error:   string(e.Code),
		Message: e.Message,
		Code:    e.Code,
		Details: e.Details,
		Context: e.Context,
	}
}

// Recovery handles panic recovery and converts to appropriate errors
func Recovery(recovered interface{}) *AppError {
	switch v := recovered.(type) {
	case *AppError:
		return v
	case error:
		return Wrap(v, ErrInternal, "Internal server error occurred")
	case string:
		return New(ErrInternal, v)
	default:
		return New(ErrInternal, "Unknown internal error occurred")
	}
}

// Chain represents a chain of errors for better error tracking
type Chain struct {
	errors []*AppError
}

// NewChain creates a new error chain
func NewChain() *Chain {
	return &Chain{
		errors: make([]*AppError, 0),
	}
}

// Add adds an error to the chain
func (c *Chain) Add(err *AppError) *Chain {
	c.errors = append(c.errors, err)
	return c
}

// HasErrors returns true if the chain has any errors
func (c *Chain) HasErrors() bool {
	return len(c.errors) > 0
}

// First returns the first error in the chain
func (c *Chain) First() *AppError {
	if len(c.errors) == 0 {
		return nil
	}
	return c.errors[0]
}

// Last returns the last error in the chain
func (c *Chain) Last() *AppError {
	if len(c.errors) == 0 {
		return nil
	}
	return c.errors[len(c.errors)-1]
}

// All returns all errors in the chain
func (c *Chain) All() []*AppError {
	return c.errors
}

// Error implements the error interface for the chain
func (c *Chain) Error() string {
	if len(c.errors) == 0 {
		return "no errors"
	}
	if len(c.errors) == 1 {
		return c.errors[0].Error()
	}
	return fmt.Sprintf("multiple errors: %s (and %d more)", c.errors[0].Error(), len(c.errors)-1)
}

// Helper functions to check error types

// IsNotFound checks if an error is a not found error
func IsNotFound(err error) bool {
	if appErr, ok := err.(*AppError); ok {
		return appErr.Code == ErrNotFound || appErr.Code == ErrApplicationNotFound || 
			   appErr.Code == ErrTokenNotFound || appErr.Code == ErrPermissionNotFound
	}
	return false
}

// IsValidationError checks if an error is a validation error
func IsValidationError(err error) bool {
	if appErr, ok := err.(*AppError); ok {
		return appErr.Code == ErrValidationFailed || appErr.Code == ErrInvalidInput ||
			   appErr.Code == ErrMissingField || appErr.Code == ErrInvalidFormat
	}
	return false
}

// IsAuthenticationError checks if an error is an authentication error
func IsAuthenticationError(err error) bool {
	if appErr, ok := err.(*AppError); ok {
		return appErr.Code == ErrUnauthorized || appErr.Code == ErrInvalidToken ||
			   appErr.Code == ErrTokenExpired || appErr.Code == ErrInvalidCredentials
	}
	return false
}

// IsAuthorizationError checks if an error is an authorization error
func IsAuthorizationError(err error) bool {
	if appErr, ok := err.(*AppError); ok {
		return appErr.Code == ErrForbidden || appErr.Code == ErrInsufficientPermissions
	}
	return false
}

// IsConflictError checks if an error is a conflict error
func IsConflictError(err error) bool {
	if appErr, ok := err.(*AppError); ok {
		return appErr.Code == ErrAlreadyExists || appErr.Code == ErrConflict
	}
	return false
}

// IsInternalError checks if an error is an internal server error
func IsInternalError(err error) bool {
	if appErr, ok := err.(*AppError); ok {
		return appErr.Code == ErrInternal || appErr.Code == ErrDatabase ||
			   appErr.Code == ErrExternal || appErr.Code == ErrTokenCreationFailed
	}
	return false
}

// IsConfigurationError checks if an error is a configuration error
func IsConfigurationError(err error) bool {
	if appErr, ok := err.(*AppError); ok {
		// Configuration errors are typically mapped to internal errors
		return appErr.Code == ErrInternal && appErr.Message != ""
	}
	return false
}
