package errs

import (
	"errors"
	"fmt"
	"testing"

	"backend/pkg/library/errors/code"

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

func TestNewBuiltinGRPCErr(t *testing.T) {
	tests := []struct {
		name      string
		scope     uint32
		detail    uint32
		msg       string
		wantCat   uint32
		wantScope uint32
		wantDet   uint32
		wantMsg   string
	}{
		{"basic", 10, 3, "test", uint32(code.CatGRPC), 10, 3, "test"},
		{"zero", 0, 0, "", uint32(code.CatGRPC), 0, 0, ""},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			e := newBuiltinGRPCErr(tt.scope, tt.detail, tt.msg)
			if e.Category() != tt.wantCat || e.Scope() != tt.wantScope || e.Detail() != tt.wantDet || e.Error() != tt.wantMsg {
				t.Errorf("newBuiltinGRPCErr = cat=%d scope=%d det=%d msg=%q, want %d %d %d %q",
					e.Category(), e.Scope(), e.Detail(), e.Error(), tt.wantCat, tt.wantScope, tt.wantDet, tt.wantMsg)
			}
		})
	}
}

func TestFromError(t *testing.T) {
	base := New(10, uint32(code.DBError), 0, "base")
	// but actually use fmt.Errorf("%w", base) for proper wrapping
	tests := []struct {
		name string
		in   error
		want *Error
	}{
		{"nil", nil, nil},
		{"not Error", errors.New("std"), nil},
		{"direct Error", base, base},
		{"wrapped Error", fmt.Errorf("wrap: %w", base), base},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := FromError(tt.in)
			if (got == nil) != (tt.want == nil) {
				t.Errorf("FromError = %v, want nil=%v", got, tt.want == nil)
			}
			if got != nil && (got.Category() != tt.want.Category() || got.Detail() != tt.want.Detail()) {
				t.Errorf("FromError = cat=%d det=%d, want %d %d", got.Category(), got.Detail(), tt.want.Category(), tt.want.Detail())
			}
		})
	}
}

func TestFromCode(t *testing.T) {
	tests := []struct {
		name      string
		code      uint32
		wantScope uint32
		wantCat   uint32
		wantDet   uint32
	}{
		{"basic", 10201123, 10, 201, 123},
		{"zero", 0, 0, 0, 0},
		{"max", 99999999, 99, 999, 999},
		{"overflow code", 100000000, 100, 0, 0}, // Parses as scope=100, but uint32 limits
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			e := FromCode(tt.code)
			if e.Scope() != tt.wantScope || e.Category() != tt.wantCat || e.Detail() != tt.wantDet {
				t.Errorf("FromCode = scope=%d cat=%d det=%d, want %d %d %d", e.Scope(), e.Category(), e.Detail(), tt.wantScope, tt.wantCat, tt.wantDet)
			}
		})
	}
}

func TestFromGRPCError(t *testing.T) {
	Scope = code.Gateway
	tests := []struct {
		name      string
		in        error
		wantScope uint32
		wantCat   uint32
		wantDet   uint32
		wantMsg   string
	}{
		{"nil", nil, 0, 0, 0, ""},
		{"builtin OK", status.New(codes.OK, "").Err(), 0, 0, 0, ""},
		{"builtin InvalidArgument", status.New(codes.InvalidArgument, "bad").Err(), uint32(code.Gateway), uint32(code.CatGRPC), uint32(codes.InvalidArgument), "bad"},
		{"custom code", status.New(codes.Code(10201123), "custom").Err(), 10, 201, 123, "custom"},
		{"unset scope with builtin", status.New(codes.NotFound, "not found").Err(), uint32(code.Gateway), uint32(code.CatGRPC), uint32(codes.NotFound), "not found"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			e := FromGRPCError(tt.in)
			if e == nil && (tt.wantScope != 0 || tt.wantCat != 0 || tt.wantDet != 0) {
				t.Errorf("got nil, want non-nil")
			}
			if e != nil && (e.Scope() != tt.wantScope || e.Category() != tt.wantCat || e.Detail() != tt.wantDet || e.Error() != tt.wantMsg) {
				t.Errorf("FromGRPCError = scope=%d cat=%d det=%d msg=%q, want %d %d %d %q",
					e.Scope(), e.Category(), e.Detail(), e.Error(), tt.wantScope, tt.wantCat, tt.wantDet, tt.wantMsg)
			}
		})
	}
}
