package errs

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"

	"gateway/internal/library/errors/code"

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

var grpcDisplayCodeRE = regexp.MustCompile(`^\[(\d{8})\]\s*(.*)$`)

// FromError unwraps err and returns the first *Error in the chain.
func FromError(err error) *Error {
	if err == nil {
		return nil
	}

	var e *Error
	if errors.As(err, &e) {
		return e
	}

	return nil
}

// FromCode parses an 8-digit code (SSCCCDDD) into an Error without a message.
func FromCode(raw uint32) (*Error, error) {
	scope := code.Scope(raw / code.ScopeMultiplier)
	sub := raw % code.ScopeMultiplier
	category := code.Category(sub / code.CategoryMultiplier)
	detail := code.Detail(sub % code.CategoryMultiplier)

	return New(scope, category, detail, "")
}

// FromGRPCError converts a gRPC status error into *Error.
// When scope is omitted, code.Unset is used for built-in gRPC status codes.
func FromGRPCError(err error, scope ...code.Scope) (*Error, error) {
	if err == nil {
		return nil, nil
	}

	sc := code.Unset
	if len(scope) > 0 {
		sc = scope[0]
	}

	s, ok := status.FromError(err)
	if !ok {
		return nil, fmt.Errorf("not a gRPC status error: %w", err)
	}

	if parsed := parseGRPCStatusMessage(s); parsed != nil {
		if parsed.Scope() == code.Unset && sc != code.Unset {
			out, scopeErr := parsed.WithScope(sc)
			if scopeErr != nil {
				return nil, scopeErr
			}

			return out.WithCause(err), nil
		}

		return parsed.WithCause(err), nil
	}

	// Standard gRPC codes (0-16): map into CatGRPC with detail = grpc code number.
	if isBuiltinGRPCCode(s.Code()) {
		return MustNew(sc, code.CatGRPC, code.Detail(s.Code()), s.Message()).WithCause(err), nil
	}

	// Custom status code carrying full business code (legacy clients).
	if e, convErr := FromCode(uint32(s.Code())); convErr == nil {
		return e.WithMessage(s.Message()).WithCause(err), nil
	}

	return nil, fmt.Errorf("unable to convert gRPC error: code=%s msg=%q", s.Code(), s.Message())
}

func parseGRPCStatusMessage(s *status.Status) *Error {
	matches := grpcDisplayCodeRE.FindStringSubmatch(s.Message())
	if len(matches) != 3 {
		return nil
	}

	raw, err := strconv.ParseUint(matches[1], 10, 32)
	if err != nil {
		return nil
	}

	e, err := FromCode(uint32(raw))
	if err != nil {
		return nil
	}

	return e.WithMessage(matches[2])
}

func isBuiltinGRPCCode(c codes.Code) bool {
	return uint32(c) <= uint32(codes.Unauthenticated)
}
