package rest

import (
	"context"
	"errors"
	"net/http"
	"sync/atomic"
	"time"

	"git.ali33.ru/fcg-xvii/go-tools/json"
	"github.com/dgrijalva/jwt-go"
)

type IServer interface {
	Addr() string
	Secret() []byte
	HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
	Listen(timeout time.Duration) error
	TokenGenerate(m json.Map, expire int64) (string, error)
	Close() error
	Context() context.Context
}

func NewServer(addr string, secret []byte) IServer {
	return &Server{
		secret: secret,
		server: &http.Server{
			Addr:    addr,
			Handler: http.NewServeMux(),
		},
	}
}

type Server struct {
	secret []byte
	server *http.Server
	opened atomic.Bool
	ctx    context.Context
}

func (s *Server) Context() context.Context {
	return s.ctx
}

func (s *Server) Close() error {
	if !s.opened.Load() {
		return errors.New("ErrNotOpened")
	}
	return s.server.Close()
}

func (s *Server) Listen(timeout time.Duration) (err error) {
	if s.opened.Swap(true) {
		return errors.New("ErrAlreadyOpened")
	}
	ctx, _ := context.WithTimeout(context.Background(), timeout)
	go func() {
		var cancel context.CancelFunc
		s.ctx, cancel = context.WithCancel(context.Background())
		err = s.server.ListenAndServe()
		s.opened.Store(false)
		cancel()
	}()
	<-ctx.Done()
	return
}

func (s *Server) TokenGenerate(m json.Map, expire int64) (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)

	claims := token.Claims.(jwt.MapClaims)
	for key, val := range m {
		claims[key] = val
	}
	if expire > 0 {
		claims["exp"] = time.Now().Add(time.Minute * 30).Unix()
	}

	tokenString, err := token.SignedString(s.Secret())
	return tokenString, err
}

func (s *Server) Addr() string {
	return s.server.Addr
}

func (s *Server) Secret() []byte {
	return s.secret
}

func (s *Server) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
	s.server.Handler.(*http.ServeMux).HandleFunc(pattern, handler)
}
