package apperrors

import (
	"errors"
	"net/http"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNotFound(t *testing.T) {
	err := NotFound("error.task_not_found")
	assert.Equal(t, http.StatusNotFound, err.Code)
	assert.Equal(t, "error.task_not_found", err.MessageKey)
	assert.Empty(t, err.Message)
	assert.Nil(t, err.Err)
}

func TestForbidden(t *testing.T) {
	err := Forbidden("error.residence_access_denied")
	assert.Equal(t, http.StatusForbidden, err.Code)
	assert.Equal(t, "error.residence_access_denied", err.MessageKey)
}

func TestBadRequest(t *testing.T) {
	err := BadRequest("error.invalid_request_body")
	assert.Equal(t, http.StatusBadRequest, err.Code)
	assert.Equal(t, "error.invalid_request_body", err.MessageKey)
}

func TestUnauthorized(t *testing.T) {
	err := Unauthorized("error.not_authenticated")
	assert.Equal(t, http.StatusUnauthorized, err.Code)
	assert.Equal(t, "error.not_authenticated", err.MessageKey)
}

func TestConflict(t *testing.T) {
	err := Conflict("error.email_taken")
	assert.Equal(t, http.StatusConflict, err.Code)
	assert.Equal(t, "error.email_taken", err.MessageKey)
}

func TestTooManyRequests(t *testing.T) {
	err := TooManyRequests("error.rate_limit_exceeded")
	assert.Equal(t, http.StatusTooManyRequests, err.Code)
	assert.Equal(t, "error.rate_limit_exceeded", err.MessageKey)
}

func TestInternal(t *testing.T) {
	underlying := errors.New("database connection failed")
	err := Internal(underlying)
	assert.Equal(t, http.StatusInternalServerError, err.Code)
	assert.Equal(t, "error.internal", err.MessageKey)
	assert.Equal(t, underlying, err.Err)
}

func TestAppError_Error_WithWrappedError(t *testing.T) {
	underlying := errors.New("connection refused")
	err := Internal(underlying).WithMessage("database error")
	assert.Equal(t, "database error: connection refused", err.Error())
}

func TestAppError_Error_WithMessageOnly(t *testing.T) {
	err := NotFound("error.task_not_found").WithMessage("Task not found")
	assert.Equal(t, "Task not found", err.Error())
}

func TestAppError_Error_MessageKeyFallback(t *testing.T) {
	err := NotFound("error.task_not_found")
	// No Message set, no Err set — should fall back to MessageKey
	assert.Equal(t, "error.task_not_found", err.Error())
}

func TestAppError_Unwrap(t *testing.T) {
	underlying := errors.New("wrapped error")
	err := Internal(underlying)
	assert.Equal(t, underlying, errors.Unwrap(err))
}

func TestAppError_Unwrap_Nil(t *testing.T) {
	err := NotFound("error.task_not_found")
	assert.Nil(t, errors.Unwrap(err))
}

func TestAppError_WithMessage(t *testing.T) {
	err := NotFound("error.task_not_found").WithMessage("custom message")
	assert.Equal(t, "custom message", err.Message)
	assert.Equal(t, "error.task_not_found", err.MessageKey)
}

func TestAppError_Wrap(t *testing.T) {
	underlying := errors.New("some error")
	err := BadRequest("error.invalid_request_body").Wrap(underlying)
	assert.Equal(t, underlying, err.Err)
	assert.Equal(t, http.StatusBadRequest, err.Code)
}

func TestAppError_ImplementsError(t *testing.T) {
	var err error = NotFound("error.task_not_found")
	assert.NotNil(t, err)
	assert.Equal(t, "error.task_not_found", err.Error())
}

func TestAppError_ErrorsAs(t *testing.T) {
	var appErr *AppError
	err := NotFound("error.task_not_found")
	assert.True(t, errors.As(err, &appErr))
	assert.Equal(t, http.StatusNotFound, appErr.Code)
}
