package errors

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

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

func TestNewAppError(t *testing.T) {
	err := NewAppError("test_code", "测试消息", http.StatusBadRequest)
	assert.Equal(t, "test_code", err.Code)
	assert.Equal(t, "测试消息", err.Message)
	assert.Equal(t, http.StatusBadRequest, err.HTTPStatus)
	assert.Nil(t, err.Cause)
	assert.Nil(t, err.Context)
}

func TestAppError_Error(t *testing.T) {
	tests := []struct {
		name     string
		err      *AppError
		expected string
	}{
		{
			name:     "无原因错误",
			err:      NewAppError("code1", "消息1", 400),
			expected: "code1: 消息1",
		},
		{
			name:     "带原因错误",
			err:      Wrap(NewAppError("code2", "消息2", 500), errors.New("原始错误")),
			expected: "code2: 消息2 (原始错误)",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, tt.err.Error())
		})
	}
}

func TestAppError_Unwrap(t *testing.T) {
	cause := errors.New("原始错误")
	err := Wrap(ErrInternal, cause)
	assert.Equal(t, cause, err.Unwrap())
}

func TestWrap(t *testing.T) {
	cause := errors.New("网络超时")
	wrapped := Wrap(ErrInternal, cause)
	assert.Equal(t, "internal_error", wrapped.Code)
	assert.Equal(t, "内部错误", wrapped.Message)
	assert.Equal(t, http.StatusInternalServerError, wrapped.HTTPStatus)
	assert.Equal(t, cause, wrapped.Cause)
}

func TestWithContext(t *testing.T) {
	err := WithContext(ErrModelNotFound, "model", "gpt-4")
	assert.Equal(t, "model_not_found", err.Code)
	assert.NotNil(t, err.Context)
	assert.Equal(t, "gpt-4", err.Context["model"])

	// 测试链式添加上下文
	err2 := WithContext(err, "provider", "openai")
	assert.Equal(t, "gpt-4", err2.Context["model"])
	assert.Equal(t, "openai", err2.Context["provider"])
}

func TestWithMessage(t *testing.T) {
	err := WithMessage(ErrInvalidRequest, "自定义错误消息")
	assert.Equal(t, "invalid_request", err.Code)
	assert.Equal(t, "自定义错误消息", err.Message)
	assert.Equal(t, http.StatusBadRequest, err.HTTPStatus)
}

func TestPredefinedErrors(t *testing.T) {
	tests := []struct {
		name       string
		err        *AppError
		code       string
		httpStatus int
	}{
		{"ErrModelNotFound", ErrModelNotFound, "model_not_found", http.StatusNotFound},
		{"ErrModelDisabled", ErrModelDisabled, "model_disabled", http.StatusNotFound},
		{"ErrProviderNotFound", ErrProviderNotFound, "provider_not_found", http.StatusNotFound},
		{"ErrProviderDisabled", ErrProviderDisabled, "provider_disabled", http.StatusNotFound},
		{"ErrInvalidRequest", ErrInvalidRequest, "invalid_request", http.StatusBadRequest},
		{"ErrInternal", ErrInternal, "internal_error", http.StatusInternalServerError},
		{"ErrDatabaseNotInit", ErrDatabaseNotInit, "database_not_initialized", http.StatusInternalServerError},
		{"ErrConflict", ErrConflict, "conflict", http.StatusConflict},
		{"ErrRequestCreate", ErrRequestCreate, "request_create_error", http.StatusInternalServerError},
		{"ErrRequestSend", ErrRequestSend, "request_send_error", http.StatusBadGateway},
		{"ErrResponseRead", ErrResponseRead, "response_read_error", http.StatusBadGateway},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.code, tt.err.Code)
			assert.Equal(t, tt.httpStatus, tt.err.HTTPStatus)
		})
	}
}

func TestAsAppError(t *testing.T) {
	t.Run("nil输入", func(t *testing.T) {
		appErr, ok := AsAppError(nil)
		assert.Nil(t, appErr)
		assert.False(t, ok)
	})

	t.Run("AppError类型", func(t *testing.T) {
		appErr, ok := AsAppError(ErrModelNotFound)
		assert.True(t, ok)
		assert.Equal(t, ErrModelNotFound, appErr)
	})

	t.Run("Wrapped AppError", func(t *testing.T) {
		wrapped := Wrap(ErrInternal, errors.New("cause"))
		appErr, ok := AsAppError(wrapped)
		assert.True(t, ok)
		assert.Equal(t, "internal_error", appErr.Code)
	})

	t.Run("非AppError类型", func(t *testing.T) {
		appErr, ok := AsAppError(errors.New("普通错误"))
		assert.Nil(t, appErr)
		assert.False(t, ok)
	})
}

func TestWithCause(t *testing.T) {
	cause := errors.New("连接超时")
	err := ErrRequestSend.WithCause(cause)
	assert.Equal(t, "request_send_error", err.Code)
	assert.Equal(t, http.StatusBadGateway, err.HTTPStatus)
	assert.Equal(t, cause, err.Cause)
	assert.True(t, errors.Is(err, cause))

	var appErr *AppError
	assert.True(t, errors.As(err, &appErr))
	assert.Equal(t, "request_send_error", appErr.Code)
}
