package api

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

func TestHTTPError_Error(t *testing.T) {
	tests := []struct {
		name     string
		err      *HTTPError
		expected string
	}{
		{
			name:     "simple error",
			err:      &HTTPError{Status: 404, Code: "NOT_FOUND", Message: "user not found"},
			expected: "user not found",
		},
		{
			name: "error with cause",
			err: &HTTPError{
				Status:  500,
				Code:    "INTERNAL_ERROR",
				Message: "database error",
				cause:   errors.New("connection refused"),
			},
			expected: "database error: connection refused",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.err.Error(); got != tt.expected {
				t.Errorf("HTTPError.Error() = %q, want %q", got, tt.expected)
			}
		})
	}
}

func TestHTTPError_Is(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		target   error
		expected bool
	}{
		{
			name:     "matches ErrNotFound",
			err:      NotFound("user not found"),
			target:   ErrNotFound,
			expected: true,
		},
		{
			name:     "matches ErrBadRequest",
			err:      BadRequest("invalid input"),
			target:   ErrBadRequest,
			expected: true,
		},
		{
			name:     "does not match different status",
			err:      NotFound("not found"),
			target:   ErrBadRequest,
			expected: false,
		},
		{
			name:     "wrapped error matches",
			err:      WrapError(ErrNotFound, errors.New("db error")),
			target:   ErrNotFound,
			expected: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := errors.Is(tt.err, tt.target); got != tt.expected {
				t.Errorf("errors.Is() = %v, want %v", got, tt.expected)
			}
		})
	}
}

func TestHTTPError_Unwrap(t *testing.T) {
	cause := errors.New("underlying error")
	err := WrapError(ErrInternal, cause)

	unwrapped := errors.Unwrap(err)
	if unwrapped != cause {
		t.Errorf("Unwrap() = %v, want %v", unwrapped, cause)
	}
}

func TestFactoryFunctions(t *testing.T) {
	tests := []struct {
		name         string
		err          error
		wantStatus   int
		wantCode     string
		wantContains string
	}{
		{
			name:         "BadRequest",
			err:          BadRequest("invalid input"),
			wantStatus:   http.StatusBadRequest,
			wantCode:     "BAD_REQUEST",
			wantContains: "invalid input",
		},
		{
			name:         "BadRequestf",
			err:          BadRequestf("field %s is invalid", "email"),
			wantStatus:   http.StatusBadRequest,
			wantCode:     "BAD_REQUEST",
			wantContains: "field email is invalid",
		},
		{
			name:         "Unauthorized",
			err:          Unauthorized("token expired"),
			wantStatus:   http.StatusUnauthorized,
			wantCode:     "UNAUTHORIZED",
			wantContains: "token expired",
		},
		{
			name:         "Forbidden",
			err:          Forbidden("access denied"),
			wantStatus:   http.StatusForbidden,
			wantCode:     "FORBIDDEN",
			wantContains: "access denied",
		},
		{
			name:         "NotFound",
			err:          NotFound("user not found"),
			wantStatus:   http.StatusNotFound,
			wantCode:     "NOT_FOUND",
			wantContains: "user not found",
		},
		{
			name:         "NotFoundf",
			err:          NotFoundf("user %s not found", "123"),
			wantStatus:   http.StatusNotFound,
			wantCode:     "NOT_FOUND",
			wantContains: "user 123 not found",
		},
		{
			name:         "Conflict",
			err:          Conflict("already exists"),
			wantStatus:   http.StatusConflict,
			wantCode:     "CONFLICT",
			wantContains: "already exists",
		},
		{
			name:         "Internal",
			err:          Internal("something went wrong"),
			wantStatus:   http.StatusInternalServerError,
			wantCode:     "INTERNAL_ERROR",
			wantContains: "something went wrong",
		},
		{
			name:         "Validation",
			err:          Validation("validation failed"),
			wantStatus:   http.StatusBadRequest,
			wantCode:     "VALIDATION_ERROR",
			wantContains: "validation failed",
		},
		{
			name:         "UnprocessableEntity",
			err:          UnprocessableEntity("semantic error"),
			wantStatus:   http.StatusUnprocessableEntity,
			wantCode:     "UNPROCESSABLE_ENTITY",
			wantContains: "semantic error",
		},
		{
			name:         "TooManyRequests",
			err:          TooManyRequests("rate limited"),
			wantStatus:   http.StatusTooManyRequests,
			wantCode:     "TOO_MANY_REQUESTS",
			wantContains: "rate limited",
		},
		{
			name:         "ServiceUnavailable",
			err:          ServiceUnavailable("maintenance"),
			wantStatus:   http.StatusServiceUnavailable,
			wantCode:     "SERVICE_UNAVAILABLE",
			wantContains: "maintenance",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			httpErr := AsHTTPError(tt.err)
			if httpErr == nil {
				t.Fatal("expected HTTPError")
			}

			if httpErr.Status != tt.wantStatus {
				t.Errorf("Status = %d, want %d", httpErr.Status, tt.wantStatus)
			}
			if httpErr.Code != tt.wantCode {
				t.Errorf("Code = %q, want %q", httpErr.Code, tt.wantCode)
			}
			if httpErr.Message != tt.wantContains {
				t.Errorf("Message = %q, want %q", httpErr.Message, tt.wantContains)
			}
		})
	}
}

func TestWithDetails(t *testing.T) {
	details := map[string]string{"field": "email", "error": "invalid format"}
	err := WithDetails(BadRequest("validation failed"), details)

	httpErr := AsHTTPError(err)
	if httpErr == nil {
		t.Fatal("expected HTTPError")
	}

	if httpErr.Details == nil {
		t.Fatal("expected Details to be set")
	}

	detailsMap, ok := httpErr.Details.(map[string]string)
	if !ok {
		t.Fatalf("expected map[string]string, got %T", httpErr.Details)
	}

	if detailsMap["field"] != "email" {
		t.Errorf("Details[field] = %q, want %q", detailsMap["field"], "email")
	}
}

func TestWithCode(t *testing.T) {
	err := WithCode(Forbidden("access denied"), "KEY_REVOKED")

	httpErr := AsHTTPError(err)
	if httpErr == nil {
		t.Fatal("expected HTTPError")
	}

	if httpErr.Code != "KEY_REVOKED" {
		t.Errorf("Code = %q, want %q", httpErr.Code, "KEY_REVOKED")
	}
	if httpErr.Status != http.StatusForbidden {
		t.Errorf("Status = %d, want %d", httpErr.Status, http.StatusForbidden)
	}
}

func TestStatusCode(t *testing.T) {
	tests := []struct {
		name       string
		err        error
		wantStatus int
	}{
		{
			name:       "HTTPError",
			err:        NotFound("not found"),
			wantStatus: http.StatusNotFound,
		},
		{
			name:       "regular error",
			err:        errors.New("some error"),
			wantStatus: http.StatusInternalServerError,
		},
		{
			name:       "nil error returns 200 OK",
			err:        nil,
			wantStatus: http.StatusOK,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := StatusCode(tt.err); got != tt.wantStatus {
				t.Errorf("StatusCode() = %d, want %d", got, tt.wantStatus)
			}
		})
	}
}

func TestIsHTTPError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{
			name:     "HTTPError",
			err:      NotFound("not found"),
			expected: true,
		},
		{
			name:     "regular error",
			err:      errors.New("some error"),
			expected: false,
		},
		{
			name:     "nil",
			err:      nil,
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := IsHTTPError(tt.err); got != tt.expected {
				t.Errorf("IsHTTPError() = %v, want %v", got, tt.expected)
			}
		})
	}
}
