package bcode

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/jinzhu/gorm"
	"github.com/pkg/errors"
)

var (
	// OK means everything si good.
	OK = new(200, 200)
	// StatusFound means the requested resource resides temporarily under a different URI.
	StatusFound = new(302, 302)
	// BadRequest means the request could not be understood by the server due to malformed syntax.
	// The client SHOULD NOT repeat the request without modifications.
	BadRequest = new(400, 400)
	// NotFound means the server has not found anything matching the request.
	NotFound = new(404, 404)
	// ServerErr means  the server encountered an unexpected condition which prevented it from fulfilling the request.
	ServerErr = new(500, 500)

	// TokenInvalid -
	TokenInvalid = new(400, 401)
)

// Coder has ability to get Code, msg or detail from error.
type Coder interface {
	// Status Code
	GetStatus() int
	// business Code
	GetCode() int
	Error() string
	Equal(err error) bool
}

var (
	codes = make(map[int]struct{})
)

func new(status, code int) Coder {
	if _, ok := codes[code]; ok {
		panic(fmt.Sprintf("bcode %d already exists", code))
	}
	codes[code] = struct{}{}
	return newCode(status, code, "")
}

func newByMessage(status, code int, message string) Coder {
	if _, ok := codes[code]; ok {
		panic(fmt.Sprintf("bcode %d already exists", code))
	}
	if status == 409 && code != 10103 {
		panic("status code 409 Don't use it for now")
	}
	codes[code] = struct{}{}
	return newCode(status, code, message)
}

// Code business a bussiness Code
type Code struct {
	Status  int    `json:"status"`
	Code    int    `json:"code"`
	Message string `json:"msg"`
}

func newCode(status, code int, message string) Coder {
	return &Code{Status: status, Code: code, Message: message}
}

// GetStatus returns the Status Code
func (c *Code) GetStatus() int {
	return c.Status
}

// GetCode returns the business Code
func (c *Code) GetCode() int {
	return c.Code
}

func (c *Code) Error() string {
	if c.Message != "" {
		return c.Message
	}
	return strconv.FormatInt(int64(c.Code), 10)
}

// Equal -
func (c *Code) Equal(err error) bool {
	obj := Err2Coder(err)
	return c.Code == obj.GetCode()
}

// Err2Coder converts the given err to Coder.
func Err2Coder(err error) Coder {
	if err == nil {
		return OK
	}
	coder, ok := errors.Cause(err).(Coder)
	if ok {
		return coder
	}
	if err == gorm.ErrRecordNotFound {
		return NotFound
	}
	return Str2Coder(err.Error())
}

// Str2Coder converts the given str to Coder.
func Str2Coder(str string) Coder {
	str = strings.TrimSpace(str)
	if str == "" {
		return OK
	}
	i, err := strconv.Atoi(str)
	if err != nil {
		return ServerErr
	}
	return newCode(400, i, "")
}

// NewBadRequest -
func NewBadRequest(msg string) Coder {
	return newCode(400, 400, msg)
}
