package models

import (
	"errors"
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
)

// CustomError defines the interface for our application's standard errors.
type CustomError interface {
	error
	// StatusCode returns the HTTP status code appropriate for this error.
	StatusCode() int
	// UserMessage returns a user-friendly message for this error.
	UserMessage() string
	// InternalCode returns a more specific internal error code or message.
	InternalCode() string
	// Unwrap returns the underlying error, if any.
	Unwrap() error
}

// baseError is a base implementation of CustomError.
type baseError struct {
	statusCode   int
	userMessage  string
	internalCode string
	cause        error
}

func (e *baseError) Error() string {
	if e.cause != nil {
		return fmt.Sprintf("%s (internal: %s): %v", e.userMessage, e.internalCode, e.cause)
	}
	return fmt.Sprintf("%s (internal: %s)", e.userMessage, e.internalCode)
}

func (e *baseError) StatusCode() int {
	return e.statusCode
}

func (e *baseError) UserMessage() string {
	return e.userMessage
}

func (e *baseError) InternalCode() string {
	return e.internalCode
}

func (e *baseError) Unwrap() error {
	return e.cause
}

// --- Specific Error Types ---

// ErrNotFound indicates that a requested resource was not found.
type ErrNotFound struct {
	baseError
}

func NewErrNotFound(internalCode string, cause error) *ErrNotFound {
	return &ErrNotFound{
		baseError{
			statusCode:   http.StatusNotFound,
			userMessage:  "The requested resource was not found.",
			internalCode: internalCode,
			cause:        cause,
		},
	}
}

// ErrValidation indicates that input data failed validation.
type ErrValidation struct {
	baseError
	// ValidationErrors can hold more specific details about which fields failed.
	ValidationErrors map[string]string
}

func NewErrValidation(internalCode string, validationErrors map[string]string, cause error) *ErrValidation {
	return &ErrValidation{
		baseError: baseError{
			statusCode:   http.StatusBadRequest,
			userMessage:  "Input validation failed. Please check your data.",
			internalCode: internalCode,
			cause:        cause,
		},
		ValidationErrors: validationErrors,
	}
}

// ErrUnauthorized indicates that the request lacks valid authentication credentials.
type ErrUnauthorized struct {
	baseError
}

func NewErrUnauthorized(internalCode string, cause error) *ErrUnauthorized {
	return &ErrUnauthorized{
		baseError{
			statusCode:   http.StatusUnauthorized,
			userMessage:  "Authentication is required and has failed or has not yet been provided.",
			internalCode: internalCode,
			cause:        cause,
		},
	}
}

// ErrForbidden indicates that the server understood the request but refuses to authorize it.
type ErrForbidden struct {
	baseError
}

func NewErrForbidden(internalCode string, cause error) *ErrForbidden {
	return &ErrForbidden{
		baseError{
			statusCode:   http.StatusForbidden,
			userMessage:  "You do not have permission to access this resource.",
			internalCode: internalCode,
			cause:        cause,
		},
	}
}

// ErrConflict indicates that the request could not be completed due to a conflict with the current state of the resource.
type ErrConflict struct {
	baseError
}

func NewErrConflict(internalCode string, cause error) *ErrConflict {
	return &ErrConflict{
		baseError{
			statusCode:   http.StatusConflict,
			userMessage:  "A conflict occurred with the current state of the resource.",
			internalCode: internalCode,
			cause:        cause,
		},
	}
}

// ErrInternalServer indicates an unexpected condition was encountered on the server.
type ErrInternalServer struct {
	baseError
}

func NewErrInternalServer(internalCode string, cause error) *ErrInternalServer {
	return &ErrInternalServer{
		baseError{
			statusCode:   http.StatusInternalServerError,
			userMessage:  "An unexpected error occurred on the server. Please try again later.",
			internalCode: internalCode,
			cause:        cause,
		},
	}
}

// ErrBadRequest indicates that the server cannot or will not process the request due to something that is perceived to be a client error.
type ErrBadRequest struct {
	baseError
}

func NewErrBadRequest(internalCode string, cause error) *ErrBadRequest {
	return &ErrBadRequest{
		baseError{
			statusCode:   http.StatusBadRequest,
			userMessage:  "The request was malformed or invalid.",
			internalCode: internalCode,
			cause:        cause,
		},
	}
}

// --- Error Predicates ---

// IsErrNotFound checks if an error (or its cause) is an ErrNotFound.
func IsErrNotFound(err error) bool {
	var e *ErrNotFound
	return errors.As(err, &e)
}

// IsErrValidation checks if an error (or its cause) is an ErrValidation.
func IsErrValidation(err error) bool {
	var e *ErrValidation
	return errors.As(err, &e)
}

// IsErrUnauthorized checks if an error (or its cause) is an ErrUnauthorized.
func IsErrUnauthorized(err error) bool {
	var e *ErrUnauthorized
	return errors.As(err, &e)
}

// IsErrForbidden checks if an error (or its cause) is an ErrForbidden.
func IsErrForbidden(err error) bool {
	var e *ErrForbidden
	return errors.As(err, &e)
}

// IsErrConflict checks if an error (or its cause) is an ErrConflict.
func IsErrConflict(err error) bool {
	var e *ErrConflict
	return errors.As(err, &e)
}

// IsErrInternalServer checks if an error (or its cause) is an ErrInternalServer.
func IsErrInternalServer(err error) bool {
	var e *ErrInternalServer
	return errors.As(err, &e)
}

// IsErrForeignKeyViolation is a placeholder for checking foreign key errors.
// This would typically be database-specific.
// For SQLite, you might check for strings like "FOREIGN KEY constraint failed".
// For Postgres, it would be a specific error code.
func IsErrForeignKeyViolation(err error) bool {
	if err == nil {
		return false
	}
	// This is a simplistic check. In a real app, you'd use driver-specific error codes/types.
	// e.g., for github.com/mattn/go-sqlite3:
	// if sqliteErr, ok := err.(sqlite3.Error); ok {
	// 	return sqliteErr.Code == sqlite3.ErrConstraintForeignKey
	// }
	return false // Placeholder, needs actual DB driver error checking
}

// --- Helper for handlers ---

// RespondWithError checks if the error is a CustomError and sends an appropriate JSON response.
// Otherwise, it sends a generic 500 error.
func RespondWithError(c GinContext, err error) {
	var customErr CustomError
	if asCustomErr, ok := err.(CustomError); ok { // Check if it directly implements CustomError
		customErr = asCustomErr
	} else if unwrapErr := AsCustomError(err); unwrapErr != nil { // Check if it wraps a CustomError
		customErr = unwrapErr
	}

	if customErr != nil {
		response := gin.H{
			"status":  "error",
			"message": customErr.UserMessage(),
			"code":    customErr.InternalCode(),
		}
		// Add validation details for validation errors
		if valErr, ok := customErr.(*ErrValidation); ok && valErr.ValidationErrors != nil {
			response["details"] = valErr.ValidationErrors
		}
		c.JSON(customErr.StatusCode(), response)
		return
	}

	// Fallback for non-custom errors (log them as they are unexpected)
	// In a real app, you'd log this error with more details.
	// log.Printf("Unhandled error: %v", err) // Example logging
	c.JSON(http.StatusInternalServerError, gin.H{
		"status":  "error",
		"message": "An unexpected internal server error occurred.",
		"code":    "INTERNAL_SERVER_ERROR",
	})
}

// GinContext is an interface to abstract gin.Context for easier testing or alternative router usage.
type GinContext interface {
	JSON(code int, obj interface{})
	// Add other gin.Context methods you use in RespondWithError if any
}

// AsCustomError attempts to unwrap err until a CustomError is found or err is nil.
func AsCustomError(err error) CustomError {
	for err != nil {
		if ce, ok := err.(CustomError); ok {
			return ce
		}
		err = Unwrap(err)
	}
	return nil
}

// Unwrap is a helper to call Unwrap on an error if the method exists.
func Unwrap(err error) error {
	u, ok := err.(interface{ Unwrap() error })
	if !ok {
		return nil
	}
	return u.Unwrap()
}

// Helper type for gin.H to avoid direct dependency in models if preferred, though gin.H is just map[string]interface{}
type ginH map[string]interface{}

// ExtractValidationErrors converts validator.ValidationErrors to a map[string]string.
func ExtractValidationErrors(err error) map[string]string {
	var ve validator.ValidationErrors
	if errors.As(err, &ve) {
		out := make(map[string]string)
		for _, fe := range ve {
			out[fe.Field()] = msgForTag(fe.Tag(), fe.Param())
		}
		return out
	}
	return nil
}

// msgForTag returns a user-friendly message for a given validation tag.
func msgForTag(tag string, param string) string {
	switch tag {
	case "required":
		return "This field is required."
	case "min":
		return fmt.Sprintf("This field must be at least %s characters long.", param)
	case "max":
		return fmt.Sprintf("This field must be at most %s characters long.", param)
	case "email":
		return "Invalid email format."
	case "oneof":
		return fmt.Sprintf("This field must be one of: %s.", param)
	// Add more cases for other tags as needed
	default:
		return "Invalid value."
	}
}
