package errors

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

	"haixun-backend/internal/library/errors/code"
)

type Error struct {
	scope    code.Scope
	category code.Category
	detail   code.Detail
	message  string
	cause    error
}

func New(scope code.Scope, category code.Category, detail code.Detail, message string) *Error {
	return &Error{scope: scope, category: category, detail: detail, message: message}
}

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

func (e *Error) Error() string {
	if e == nil {
		return ""
	}
	return e.message
}

func (e *Error) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.cause
}

func (e *Error) WithCause(cause error) *Error {
	if e == nil {
		return nil
	}
	cp := *e
	cp.cause = cause
	return &cp
}

func (e *Error) Scope() code.Scope {
	if e == nil {
		return code.Unset
	}
	return e.scope
}

func (e *Error) Category() code.Category {
	if e == nil {
		return code.SysInternal
	}
	return e.category
}

func (e *Error) Detail() code.Detail {
	if e == nil {
		return code.DefaultDetail
	}
	return e.detail
}

func (e *Error) Code() uint32 {
	if e == nil {
		return 0
	}
	return uint32(e.scope)*code.ScopeMultiplier + uint32(e.category)*code.CategoryMultiplier + uint32(e.detail)
}

func (e *Error) DisplayCode() string {
	if e == nil {
		return "00000000"
	}
	return fmt.Sprintf("%08d", e.Code())
}

func (e *Error) HTTPStatus() int {
	if e == nil {
		return http.StatusInternalServerError
	}
	switch e.category {
	case code.InputInvalidFormat, code.InputMissingRequired:
		return http.StatusBadRequest
	case code.DBUnavailable:
		return http.StatusServiceUnavailable
	case code.ResNotFound:
		return http.StatusNotFound
	case code.ResConflict:
		return http.StatusConflict
	case code.ResInvalidState:
		return http.StatusConflict
	case code.AuthUnauthorized:
		return http.StatusUnauthorized
	case code.AuthForbidden:
		return http.StatusForbidden
	case code.SvcThirdParty:
		return http.StatusBadGateway
	default:
		return http.StatusInternalServerError
	}
}

type Builder struct {
	scope code.Scope
}

func For(scope code.Scope) Builder {
	return Builder{scope: scope}
}

func (b Builder) InputInvalidFormat(message string) *Error {
	return New(b.scope, code.InputInvalidFormat, 0, message)
}

func (b Builder) InputMissingRequired(message string) *Error {
	return New(b.scope, code.InputMissingRequired, 0, message)
}

func (b Builder) DBError(message string) *Error {
	return New(b.scope, code.DBError, 0, message)
}

func (b Builder) DBUnavailable(message string) *Error {
	return New(b.scope, code.DBUnavailable, 0, message)
}

func (b Builder) ResNotFound(message string) *Error {
	return New(b.scope, code.ResNotFound, 0, message)
}

func (b Builder) ResConflict(message string) *Error {
	return New(b.scope, code.ResConflict, 0, message)
}

func (b Builder) ResInvalidState(message string) *Error {
	return New(b.scope, code.ResInvalidState, 0, message)
}

func (b Builder) AuthUnauthorized(message string) *Error {
	return New(b.scope, code.AuthUnauthorized, 0, message)
}

func (b Builder) AuthForbidden(message string) *Error {
	return New(b.scope, code.AuthForbidden, 0, message)
}

func (b Builder) SysInternal(message string) *Error {
	return New(b.scope, code.SysInternal, 0, message)
}

func (b Builder) SysNotImplemented(message string) *Error {
	return New(b.scope, code.SysNotImplemented, 0, message)
}

func (b Builder) SvcThirdParty(message string) *Error {
	return New(b.scope, code.SvcThirdParty, 0, message)
}
