package errs

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

	"backend/pkg/library/errs/code"

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

// Scope 全域變數應由服務或模組設置
var Scope = code.Unset

// LibError 7 碼，服務 2 碼,詳細錯誤 2 碼 ，Cat 3 碼 不參與，獨立的 code 組成為（ 000 category + 00 detail）
type LibError struct {
	scope       uint32 // 系統代號，*100000 來操作，顯示時不夠會補足 7 位數， Library 定義 -> 輸入時都只要輸入兩位數
	category    uint32 // 類別代碼 Library 定義 -> 不客製化業務訊息時，用這個大類別來給錯誤 3 碼
	code        uint32 // 細項 ，每個 repo 裡自行定義 -> 一萬以下都可以  2 碼
	msg         string // 顯示用的，給前端看的 Msg
	internalErr error  // 紀錄且包含真正的錯誤，通常用這個
}

// Error 是錯誤的介面
// 私有屬性 "displayMsg" 的 getter 函數，這邊只顯示業務邏輯錯誤，因為可能會帶出去給客戶端
// 要如何定位系統真的發生什麼錯？請使用 internalErr 來做定位，通常是會印 Log 的所以用這個
func (e *LibError) Error() string {
	if e == nil {
		return ""
	}

	return e.msg
}

// Category 私有屬性 "category" 的 getter 函數
func (e *LibError) Category() uint32 {
	if e == nil {
		return 0
	}

	return e.category
}

// Scope 私有屬性 "scope" 的 getter 函數
func (e *LibError) Scope() uint32 {
	if e == nil {
		return code.Unset
	}

	return e.scope
}

// Code 私有屬性 "code" 的 getter 函數
func (e *LibError) Code() uint32 {
	if e == nil {
		return code.OK
	}

	return e.code
}

func (e *LibError) FullCode() uint32 {
	if e == nil {
		return 0
	}

	return e.Scope()*100000 + e.Code()
}

// DisplayErrorCode 要顯示的 Error Code
func (e *LibError) DisplayErrorCode() string {
	if e == nil {
		return "000000"
	}

	return fmt.Sprintf("%06d", e.FullCode())
}

// InternalError 帶入真正的 error
func (e *LibError) InternalError() error {
	var err error = fmt.Errorf("failed to get internal error")
	if e == nil {
		return err
	}

	if e.internalErr != nil {
		err = e.internalErr
	}

	return err
}

// GeneralError 轉換 category 級別錯誤訊息，模糊化
func (e *LibError) GeneralError() string {
	if e == nil {
		return ""
	}

	errStr, ok := code.CatToStr[e.Category()]
	if !ok {
		return ""
	}

	return errStr
}

// Is 在執行 errors.Is() 時調用。
// 除非你非常確定你在做什麼，否則不要直接使用這個函數。
// 請使用 errors.Is 代替。
// 此函數比較兩個錯誤變量是否都是 *Err，並且具有相同的 code（不檢查包裹的內部錯誤）
func (e *LibError) Is(f error) bool {
	var err *LibError
	ok := errors.As(f, &err)
	if !ok {
		return false
	}

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

// Unwrap 返回底層錯誤
// 解除包裹錯誤的結果本身可能具有 Unwrap 方法;
// 我們稱通過反覆解除包裹產生的錯誤序列為錯誤鏈。
func (e *LibError) Unwrap() error {
	if e == nil {
		return nil
	}

	return e.internalErr
}

// Wrap 將內部錯誤設置到 Err 結構
func (e *LibError) Wrap(internalErr error) *LibError {
	if e != nil {
		e.internalErr = internalErr
	}

	return e
}

func (e *LibError) GRPCStatus() *status.Status {
	if e == nil {
		return status.New(codes.OK, "")
	}

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

// HTTPStatus 返回對應的 HTTP 狀態碼
func (e *LibError) HTTPStatus() int {
	// 如果錯誤為空或錯誤碼為 OK，則返回 200 狀態碼
	if e == nil || e.Code() == code.OK {
		return http.StatusOK
	}
	// 根據錯誤碼判斷對應的 HTTP 狀態碼
	switch e.Code() / 100 {
	case code.ResourceInsufficient, code.InvalidFormat:
		// 如果資源不足，返回 400 狀態碼
		return http.StatusBadRequest
	case code.Unauthorized, code.InsufficientPermission:
		// 如果未授權或權限不足，返回 401 狀態碼
		return http.StatusUnauthorized
	case code.InsufficientQuota:
		// 如果配額不足，返回 402 狀態碼
		return http.StatusPaymentRequired
	case code.InvalidPosixTime, code.Forbidden, code.UserSuspended:
		// 如果時間無效或禁止訪問，返回 403 狀態碼
		return http.StatusForbidden
	case code.ResourceNotFound:
		// 如果資源未找到，返回 404 狀態碼
		return http.StatusNotFound
	case code.ResourceAlreadyExist, code.InvalidResourceState:
		// 如果資源已存在或狀態無效，返回 409 狀態碼
		return http.StatusConflict
	case code.NotValidImplementation:
		// 如果實現無效，返回 501 狀態碼
		return http.StatusNotImplemented
	default:
		// 如果沒有匹配的錯誤碼，則繼續下一步
	}

	// 根據錯誤的類別判斷對應的 HTTP 狀態碼
	switch e.Category() {
	case code.CatInput:
		// 如果錯誤屬於輸入錯誤類別，返回 400 狀態碼
		return http.StatusBadRequest
	default:
		// 如果沒有符合的條件，返回 500 狀態碼
		return http.StatusInternalServerError
	}
}

// NewError 創建新的 Error
// 確保 category 在 0 到 999 之間，將超出的 category 設為最大值 999
// 確保 detail 在 0 到 99 之間，將超出的 detail 設為最大值 99
func NewError(scope, category, detail uint32, displayMsg string) *LibError {
	// 確保 category 在 0 到 999 之間
	if category > 999 {
		category = 999 // 將超出的 category 設為最大值 999
	}

	// 確保 detail 在 0 到 99 之間
	if detail > 99 {
		detail = 99 // 將超出的 detail 設為最大值 99
	}

	return &LibError{
		category: category,
		code:     category*100 + detail,
		scope:    scope,
		msg:      displayMsg,
	}
}
