package errs

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

	"backend/pkg/library/errors/code"

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

func TestNew(t *testing.T) {
	tests := []struct {
		name         string
		scope        uint32
		category     uint32
		detail       uint32
		displayMsg   string
		wantScope    uint32
		wantCategory uint32
		wantDetail   uint32
		wantMsg      string
	}{
		{"basic", 10, 201, 123, "test", 10, 201, 123, "test"},
		{"clamp category", 10, 1000, 0, "clamp cat", 10, 999, 0, "clamp cat"},
		{"clamp detail", 10, 101, 1000, "clamp det", 10, 101, 999, "clamp det"},
		{"zero values", 0, 0, 0, "", 0, 0, 0, ""},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			e := New(tt.scope, tt.category, tt.detail, tt.displayMsg)
			if e.Scope() != tt.wantScope || e.Category() != tt.wantCategory || e.Detail() != tt.wantDetail || e.msg != tt.wantMsg {
				t.Errorf("New() = %+v, want scope=%d cat=%d det=%d msg=%q", e, tt.wantScope, tt.wantCategory, tt.wantDetail, tt.wantMsg)
			}
		})
	}
}

func TestErrorMethods(t *testing.T) {
	e := New(10, 201, 123, "test error")
	tests := []struct {
		name      string
		err       *Error
		wantErr   string
		wantScope uint32
		wantCat   uint32
		wantDet   uint32
	}{
		{"non-nil", e, "test error", 10, 201, 123},
		{"nil", nil, "", uint32(code.Unset), uint32(code.DefaultCategory), uint32(code.DefaultDetail)}, // Adjust if Default* not defined; use 0
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.err.Error(); got != tt.wantErr {
				t.Errorf("Error() = %q, want %q", got, tt.wantErr)
			}
			if got := tt.err.Scope(); got != tt.wantScope {
				t.Errorf("Scope() = %d, want %d", got, tt.wantScope)
			}
			if got := tt.err.Category(); got != tt.wantCat {
				t.Errorf("Category() = %d, want %d", got, tt.wantCat)
			}
			if got := tt.err.Detail(); got != tt.wantDet {
				t.Errorf("Detail() = %d, want %d", got, tt.wantDet)
			}
		})
	}
}

func TestCodes(t *testing.T) {
	tests := []struct {
		name        string
		err         *Error
		wantSubCode uint32
		wantCode    uint32
		wantDisplay string
	}{
		{"basic", New(10, 201, 123, ""), 201123, 10201123, "10201123"},
		{"nil", nil, code.OK, code.NonCode, "00000000"},
		{"max clamp", New(99, 999, 999, ""), 999999, 99999999, "99999999"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.err.SubCode(); got != tt.wantSubCode {
				t.Errorf("SubCode() = %d, want %d", got, tt.wantSubCode)
			}
			if got := tt.err.Code(); got != tt.wantCode {
				t.Errorf("Code() = %d, want %d", got, tt.wantCode)
			}
			if got := tt.err.DisplayCode(); got != tt.wantDisplay {
				t.Errorf("DisplayCode() = %q, want %q", got, tt.wantDisplay)
			}
		})
	}
}

func TestIs(t *testing.T) {
	e1 := New(10, 201, 123, "")
	e2 := New(10, 201, 123, "") // same subcode
	e3 := New(10, 202, 123, "") // different category
	stdErr := errors.New("std")
	tests := []struct {
		name   string
		err    error
		target error
		want   bool
	}{
		{"match", e1, e2, true},
		{"mismatch", e1, e3, false},
		{"not Error type", e1, stdErr, false},
		{"nil err", nil, e2, false},
		{"nil target", e1, nil, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := errors.Is(tt.err, tt.target); got != tt.want {
				t.Errorf("Is() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestWrapUnwrap(t *testing.T) {
	internal := errors.New("internal")
	tests := []struct {
		name       string
		err        *Error
		wrapErr    error
		wantUnwrap error
	}{
		{"wrap non-nil", New(10, 201, 0, ""), internal, internal},
		{"wrap nil", nil, internal, nil}, // Wrap on nil does nothing
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := tt.err.Wrap(tt.wrapErr)
			if unwrapped := got.Unwrap(); unwrapped != tt.wantUnwrap {
				t.Errorf("Unwrap() = %v, want %v", unwrapped, tt.wantUnwrap)
			}
		})
	}
}

func TestGRPCStatus(t *testing.T) {
	tests := []struct {
		name     string
		err      *Error
		wantCode codes.Code
		wantMsg  string
	}{
		{"non-nil", New(10, 201, 123, "grpc err"), codes.Code(10201123), "grpc err"},
		{"nil", nil, codes.OK, ""},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := tt.err.GRPCStatus()
			if s.Code() != tt.wantCode || s.Message() != tt.wantMsg {
				t.Errorf("GRPCStatus() = code=%v msg=%q, want code=%v msg=%q", s.Code(), s.Message(), tt.wantCode, tt.wantMsg)
			}
		})
	}
}

func TestHTTPStatus(t *testing.T) {
	tests := []struct {
		name string
		err  *Error
		want int
	}{
		{"nil", nil, http.StatusOK},
		{"OK subcode", New(10, 0, 0, ""), http.StatusOK},
		{"InputInvalidFormat", New(10, uint32(code.InputInvalidFormat), 0, ""), http.StatusBadRequest},
		{"InputNotValidImplementation", New(10, uint32(code.InputNotValidImplementation), 0, ""), http.StatusUnprocessableEntity},
		{"DBError", New(10, uint32(code.DBError), 0, ""), http.StatusInternalServerError},
		{"ResNotFound", New(10, uint32(code.ResNotFound), 0, ""), http.StatusNotFound},
		// Add all other categories to cover switch branches
		{"InputInvalidRange", New(10, uint32(code.InputInvalidRange), 0, ""), http.StatusUnprocessableEntity},
		{"DBDataConvert", New(10, uint32(code.DBDataConvert), 0, ""), http.StatusUnprocessableEntity},
		{"DBDuplicate", New(10, uint32(code.DBDuplicate), 0, ""), http.StatusConflict},
		{"ResInvalidFormat", New(10, uint32(code.ResInvalidFormat), 0, ""), http.StatusUnprocessableEntity},
		{"ResAlreadyExist", New(10, uint32(code.ResAlreadyExist), 0, ""), http.StatusConflict},
		{"ResInsufficient", New(10, uint32(code.ResInsufficient), 0, ""), http.StatusBadRequest},
		{"ResInsufficientPerm", New(10, uint32(code.ResInsufficientPerm), 0, ""), http.StatusForbidden},
		{"ResInvalidMeasureID", New(10, uint32(code.ResInvalidMeasureID), 0, ""), http.StatusBadRequest},
		{"ResExpired", New(10, uint32(code.ResExpired), 0, ""), http.StatusGone},
		{"ResMigrated", New(10, uint32(code.ResMigrated), 0, ""), http.StatusGone},
		{"ResInvalidState", New(10, uint32(code.ResInvalidState), 0, ""), http.StatusConflict},
		{"ResInsufficientQuota", New(10, uint32(code.ResInsufficientQuota), 0, ""), http.StatusTooManyRequests},
		{"ResMultiOwner", New(10, uint32(code.ResMultiOwner), 0, ""), http.StatusConflict},
		{"AuthUnauthorized", New(10, uint32(code.AuthUnauthorized), 0, ""), http.StatusUnauthorized},
		{"AuthExpired", New(10, uint32(code.AuthExpired), 0, ""), http.StatusUnauthorized},
		{"AuthInvalidPosixTime", New(10, uint32(code.AuthInvalidPosixTime), 0, ""), http.StatusUnauthorized},
		{"AuthSigPayloadMismatch", New(10, uint32(code.AuthSigPayloadMismatch), 0, ""), http.StatusUnauthorized},
		{"AuthForbidden", New(10, uint32(code.AuthForbidden), 0, ""), http.StatusForbidden},
		{"SysTooManyRequest", New(10, uint32(code.SysTooManyRequest), 0, ""), http.StatusTooManyRequests},
		{"SysInternal", New(10, uint32(code.SysInternal), 0, ""), http.StatusInternalServerError},
		{"SysMaintain", New(10, uint32(code.SysMaintain), 0, ""), http.StatusServiceUnavailable},
		{"SysTimeout", New(10, uint32(code.SysTimeout), 0, ""), http.StatusGatewayTimeout},
		{"PSuPublish", New(10, uint32(code.PSuPublish), 0, ""), http.StatusBadGateway},
		{"PSuConsume", New(10, uint32(code.PSuConsume), 0, ""), http.StatusBadGateway},
		{"PSuTooLarge", New(10, uint32(code.PSuTooLarge), 0, ""), http.StatusRequestEntityTooLarge},
		{"SvcMaintenance", New(10, uint32(code.SvcMaintenance), 0, ""), http.StatusServiceUnavailable},
		{"SvcInternal", New(10, uint32(code.SvcInternal), 0, ""), http.StatusInternalServerError},
		{"SvcThirdParty", New(10, uint32(code.SvcThirdParty), 0, ""), http.StatusBadGateway},
		{"SvcHTTP400", New(10, uint32(code.SvcHTTP400), 0, ""), http.StatusBadRequest},
		{"fallback unknown", New(10, 999, 0, ""), http.StatusInternalServerError},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.err.HTTPStatus(); got != tt.want {
				t.Errorf("HTTPStatus() = %d, want %d", got, tt.want)
			}
		})
	}
}
