package errs

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

	"backend/pkg/library/errs/code"

	"google.golang.org/grpc/codes"
)

func TestNewError(t *testing.T) {
	tests := []struct {
		name        string
		scope       uint32
		category    uint32
		detail      uint32
		displayMsg  string
		expectedMsg string
		expectedCat uint32
		expectedDet uint32
	}{
		{"valid error", 1, 200, 10, "test error", "test error", 200, 20010},
		{"category overflow", 1, 1000, 10, "test error", "test error", 999, 99910},
		{"detail overflow", 1, 200, 150, "test error", "test error", 200, 20099},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := NewError(tt.scope, tt.category, tt.detail, tt.displayMsg)
			if err.Error() != tt.expectedMsg {
				t.Errorf("expected %s, got %s", tt.expectedMsg, err.Error())
			}
			if err.Category() != tt.expectedCat {
				t.Errorf("expected category %d, got %d", tt.expectedCat, err.Category())
			}
			if err.Code() != tt.expectedDet {
				t.Errorf("expected code %d, got %d", tt.expectedDet, err.Code())
			}
		})
	}
}

func TestLibError_FullCode(t *testing.T) {
	tests := []struct {
		name         string
		scope        uint32
		category     uint32
		detail       uint32
		expectedCode uint32
	}{
		{"valid code", 1, 200, 10, 120010},
		{"category overflow", 1, 1000, 10, 199910},
		{"detail overflow", 1, 200, 150, 120099},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := NewError(tt.scope, tt.category, tt.detail, "test")
			if err.FullCode() != tt.expectedCode {
				t.Errorf("expected %d, got %d", tt.expectedCode, err.FullCode())
			}
		})
	}
}

func TestLibError_HTTPStatus(t *testing.T) {
	tests := []struct {
		name     string
		err      *LibError
		expected int
	}{
		{"bad request", NewError(1, code.CatService, code.ResourceInsufficient, "bad request"), http.StatusBadRequest},
		{"unauthorized", NewError(1, code.CatAuth, code.Unauthorized, "unauthorized"), http.StatusUnauthorized},
		{"forbidden", NewError(1, code.CatAuth, code.Forbidden, "forbidden"), http.StatusForbidden},
		{"not found", NewError(1, code.CatResource, code.ResourceNotFound, "not found"), http.StatusNotFound},
		{"internal server error", NewError(1, code.CatDB, 1095, "not found"), http.StatusInternalServerError},
		{"input err", NewError(1, code.CatInput, 1095, "not found"), http.StatusBadRequest},
	}

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

func TestLibError_Error(t *testing.T) {
	err := NewError(0, 100, 5, "test error")
	expected := "test error"
	if err.Error() != expected {
		t.Errorf("expected '%s', got '%s'", expected, err.Error())
	}
}

func TestLibError_Is(t *testing.T) {
	err1 := NewError(0, 1, 1, "error 1")
	err2 := NewError(0, 1, 1, "error 2")
	err3 := errors.New("other error")

	if !err1.Is(err2) {
		t.Error("expected errors to be equal")
	}

	if err1.Is(err3) {
		t.Error("expected errors to not be equal")
	}
}

func TestLibError_DisplayErrorCode(t *testing.T) {
	tests := []struct {
		name     string
		err      *LibError
		expected string
	}{
		{"valid code", NewError(1, 200, 10, "test error"), "120010"},
		{"nil error", nil, "000000"},
	}

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

func TestLibError_Unwrap(t *testing.T) {
	originalErr := errors.New("original error")
	libErr := NewError(0, 1, 1, "wrapped error").Wrap(originalErr)

	if unwrappedErr := libErr.Unwrap(); unwrappedErr != originalErr {
		t.Errorf("expected original error, got %v", unwrappedErr)
	}
}

func TestLibError_InternalError(t *testing.T) {
	tests := []struct {
		name        string
		internalErr error
		expected    error
	}{
		{"valid internal error", errors.New("internal"), errors.New("internal")},
		{"nil internal error", nil, errors.New("failed to get internal error")},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := NewError(1, 200, 10, "test").Wrap(tt.internalErr)
			if internalErr := err.InternalError(); internalErr.Error() != tt.expected.Error() {
				t.Errorf("expected %v, got %v", tt.expected, internalErr)
			}
		})
	}
}

func TestLibError_GRPCStatus(t *testing.T) {
	tests := []struct {
		name     string
		err      *LibError
		expected codes.Code
	}{
		{"valid GRPC status", NewError(1, 200, 10, "test error"), codes.Code(120010)},
		{"nil error", nil, codes.OK},
	}

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