package errs

import (
	"errors"
	"fmt"
	"net/http"

	"backend/pkg/library/errors/code"

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

// Scope is a global variable that should be set by the service or module.
var Scope = code.Unset

// Error represents a structured error with an 8-digit code.
// The code is composed of a 2-digit scope, a 3-digit category, and a 3-digit detail.
// Format: SSCCCDDD
type Error struct {
	scope       uint32 // 2-digit service scope
	category    uint32 // 3-digit category
	detail      uint32 // 3-digit detail
	msg         string // Display message for the client
	internalErr error  // The actual underlying error
}

// New creates a new Error.
// It ensures that category is within 0-999 and detail is within 0-999.
func New(scope, category, detail uint32, displayMsg string) *Error {
	if category > uint32(code.MaxCategory) {
		category = uint32(code.ReservedMaxCategory)
	}
	if detail > uint32(code.MaxDetail) {
		detail = uint32(code.ReservedMaxDetail)
	}

	return &Error{
		scope:    scope,
		category: category,
		detail:   detail,
		msg:      displayMsg,
	}
}

// Error returns the display message. This is intended for the client.
// For internal logging and debugging, use Unwrap() to get the underlying error.
func (e *Error) Error() string {
	if e == nil {
		return ""
	}

	return e.msg
}

// Scope returns the 2-digit scope of the error.
func (e *Error) Scope() uint32 {
	if e == nil {
		return uint32(code.Unset)
	}

	return e.scope
}

// Category returns the 3-digit category of the error.
func (e *Error) Category() uint32 {
	if e == nil {
		return uint32(code.DefaultCategory)
	}

	return e.category
}

// Detail returns the 2-digit detail code of the error.
func (e *Error) Detail() uint32 {
	if e == nil {
		return uint32(code.DefaultDetail)
	}

	return e.detail
}

// SubCode returns the 6-digit code (category + detail).
func (e *Error) SubCode() uint32 {
	if e == nil {
		return code.OK
	}
	c := e.category*code.CategoryMultiplier + e.detail

	return c
}

// Code returns the full 8-digit error code (scope + category + detail).
func (e *Error) Code() uint32 {
	if e == nil {
		return code.NonCode
	}

	return e.Scope()*code.ScopeMultiplier + e.SubCode()
}

// DisplayCode returns the 8-digit error code as a zero-padded string.
func (e *Error) DisplayCode() string {
	if e == nil {
		return "00000000"
	}

	return fmt.Sprintf("%08d", e.Code())
}

// Is checks if the target error is of type *Error and has the same sub-code.
// It is called by errors.Is(). Do not use it directly.
func (e *Error) Is(target error) bool {
	var err *Error
	if !errors.As(target, &err) {
		return false
	}

	return e.SubCode() == err.SubCode()
}

// Unwrap returns the underlying wrapped error.
func (e *Error) Unwrap() error {
	if e == nil {
		return nil
	}

	return e.internalErr
}

// Wrap sets the internal error for the current error.
func (e *Error) Wrap(internalErr error) *Error {
	if e != nil {
		e.internalErr = internalErr
	}

	return e
}

// GRPCStatus converts the error to a gRPC status.
func (e *Error) GRPCStatus() *status.Status {
	if e == nil {
		return status.New(codes.OK, "")
	}

	return status.New(codes.Code(e.Code()), e.Error())
}

// HTTPStatus returns the corresponding HTTP status code for the error.
func (e *Error) HTTPStatus() int {
	if e == nil || e.SubCode() == code.OK {
		return http.StatusOK
	}

	switch e.Category() {
	// Input
	case uint32(code.InputInvalidFormat):
		return http.StatusBadRequest // 400：輸入格式錯
	case uint32(code.InputNotValidImplementation),
		uint32(code.InputInvalidRange):
		return http.StatusUnprocessableEntity // 422：語意正確但無法處理（範圍/實作）

	// DB
	case uint32(code.DBError):
		return http.StatusInternalServerError // 500：後端暫時性故障（若你偏好 503 可自行調整）
	case uint32(code.DBDataConvert):
		return http.StatusUnprocessableEntity // 422：可修正的資料轉換失敗
	case uint32(code.DBDuplicate):
		return http.StatusConflict // 409：唯一鍵/重複

	// Resource
	case uint32(code.ResNotFound):
		return http.StatusNotFound // 404：資源不存在
	case uint32(code.ResInvalidFormat):
		return http.StatusUnprocessableEntity // 422：資源表示/格式不符
	case uint32(code.ResAlreadyExist):
		return http.StatusConflict // 409：已存在
	case uint32(code.ResInsufficient):
		return http.StatusBadRequest // 400：數量/容量/條件不足（可由客戶端修正）
	case uint32(code.ResInsufficientPerm):
		return http.StatusForbidden // 403：資源層面的權限不足
	case uint32(code.ResInvalidMeasureID):
		return http.StatusBadRequest // 400：ID 無效
	case uint32(code.ResExpired):
		return http.StatusGone // 410：資源已過期/不可用
	case uint32(code.ResMigrated):
		return http.StatusGone // 410：已遷移（若需導引可由上層加 Location）
	case uint32(code.ResInvalidState):
		return http.StatusConflict // 409：目前狀態不允許此操作
	case uint32(code.ResInsufficientQuota):
		return http.StatusTooManyRequests // 429：配額不足/達上限
	case uint32(code.ResMultiOwner):
		return http.StatusConflict // 409：多所有者衝突

	// Auth
	case uint32(code.AuthUnauthorized),
		uint32(code.AuthExpired),
		uint32(code.AuthInvalidPosixTime),
		uint32(code.AuthSigPayloadMismatch):
		return http.StatusUnauthorized // 401：未驗證/無效憑證
	case uint32(code.AuthForbidden):
		return http.StatusForbidden // 403：有身分但沒權限

	// System
	case uint32(code.SysTooManyRequest):
		return http.StatusTooManyRequests // 429：節流
	case uint32(code.SysInternal):
		return http.StatusInternalServerError // 500：系統內部錯
	case uint32(code.SysMaintain):
		return http.StatusServiceUnavailable // 503：維護中
	case uint32(code.SysTimeout):
		return http.StatusGatewayTimeout // 504：處理/下游逾時

	// PubSub
	case uint32(code.PSuPublish),
		uint32(code.PSuConsume):
		return http.StatusBadGateway // 502：訊息中介/外部匯流排失敗
	case uint32(code.PSuTooLarge):
		return http.StatusRequestEntityTooLarge // 413：訊息太大

	// Service
	case uint32(code.SvcMaintenance):
		return http.StatusServiceUnavailable // 503：服務維護
	case uint32(code.SvcInternal):
		return http.StatusInternalServerError // 500：服務內部錯
	case uint32(code.SvcThirdParty):
		return http.StatusBadGateway // 502：第三方依賴失敗
	case uint32(code.SvcHTTP400):
		return http.StatusBadRequest // 400：明確指派 400
	}

	// fallback
	return http.StatusInternalServerError
}
