package lib

import (
	"errors"
	"fmt"
	"go.uber.org/zap/zapcore"
	"runtime"
)

type code uint8

const (
	ErrValidationFailed code = 1
	ErrInternal         code = 2
	ErrExternal         code = 3
	ErrNoPermission     code = 4
	ErrUnknown          code = 5
	ErrDeadlineExceeded code = 6
	ErrNotFound         code = 7
	ErrAlreadyExists    code = 8
	ErrConflict         code = 9
	ErrUnimplemented    code = 10
	ErrBadInput         code = 11
	ErrUnauthenticated  code = 12
)

func (c code) String() string {
	switch {
	case errors.Is(c, ErrValidationFailed):
		return "validation error"
	case errors.Is(c, ErrInternal):
		return "internal error"
	case errors.Is(c, ErrExternal):
		return "external error"
	case errors.Is(c, ErrNoPermission):
		return "permission error"
	case errors.Is(c, ErrUnknown):
		return "unknown error"
	case errors.Is(c, ErrDeadlineExceeded):
		return "deadline error"
	case errors.Is(c, ErrNotFound):
		return "not found error"
	case errors.Is(c, ErrAlreadyExists):
		return "already exists error"
	case errors.Is(c, ErrConflict):
		return "conflict error"
	case errors.Is(c, ErrUnimplemented):
		return "unimplemented error"
	case errors.Is(c, ErrBadInput):
		return "bad input error"
	}

	panic("unimplemented")
}

func (c code) Error() string {
	return c.String()
}

type layer uint8

const (
	LayerTransport layer = 1
	LayerService   layer = 2
	LayerStorage   layer = 3
)

func (l layer) String() string {
	switch l {
	case LayerTransport:
		return "transport"
	case LayerService:
		return "service"
	case LayerStorage:
		return "storage"
	}

	panic("unimplemented")
}

func location(skip int) string {
	_, file, line, _ := runtime.Caller(skip)
	return fmt.Sprintf("%s:%d", file, line)
}

type Error struct {
	src   error
	layer layer
	code  code
	msg   string
	loc   string
}

func wrap(src error, layer layer, class code, msg string, loc string) *Error {
	return &Error{
		src:   src,
		layer: layer,
		code:  class,
		msg:   msg,
		loc:   loc,
	}
}

func (e *Error) Unwrap() []error {
	return []error{e.src, e.code}
}

func (e *Error) Error() string {
	return fmt.Sprintf("%s: %s", e.code.String(), e.msg)
}

func (e *Error) MarshalLogObject(encoder zapcore.ObjectEncoder) error {
	if e.src != nil {
		encoder.AddString("src", e.src.Error())
	}
	encoder.AddString("layer", e.layer.String())
	encoder.AddString("code", e.code.String())
	encoder.AddString("msg", e.msg)
	return nil
}

func TransportError(src error, code code, msg string) error {
	return wrap(src, LayerTransport, code, msg, location(2))
}

func ServiceError(src error, code code, msg string) error {
	return wrap(src, LayerService, code, msg, location(2))
}

func StorageError(src error, code code, msg string) error {
	return wrap(src, LayerStorage, code, msg, location(2))
}
