package rest

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

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

// IServer реализует интерфейс сервера для рест апи
type IServer interface {
	// Addr возвращеат адрес, который сервер слушает
	Addr() string
	// Secret возвращает секретный ключ, при помощи которого генерируются токены авторизации пользователей
	Secret() []byte
	// HandleFunc устанавливает функцию обработчик, которая будет вызвана по обращению к URL, удовлетворяющему pattern
	HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
	// Listen запускает прослушивание сервера. Если не удалось успешно запустить прослушивание через
	// заданный временной интервал (timeout), будет возвращена ошибка
	Listen(timeout time.Duration, ctxParent context.Context) error
	// TokenGenerate - функция генерации токена авторизации
	TokenGenerate(m json.Map, expire int64) (string, error)
	// TokenParse - функция для расшифровки токена в карту значений
	TokenParse(token string) (json.Map, error)
	// Close останавливает прослушивание
	Close() error
	// Context возвращает контекст сервера
	Context() context.Context
}

// NewServer создает сервер для реализации рест апи
func NewServer(addr string, secret []byte) IServer {
	return &Server{
		secret: secret,
		server: &http.Server{
			Addr:    addr,
			Handler: http.NewServeMux(),
		},
	}
}

func NewServerTLS(addr string, secret []byte, tlsKey, tlsCert string) IServer {
	serv := NewServer(addr, secret).(*Server)
	serv.tlsEnabled = true
	serv.tlsKey = tlsKey
	serv.tlsCert = tlsCert
	return serv
}

// Server реализует сервер для рест апи
type Server struct {
	secret     []byte
	server     *http.Server
	opened     atomic.Bool
	ctx        context.Context
	cancel     context.CancelFunc
	tlsEnabled bool
	tlsKey     string
	tlsCert    string
}

// Context возвращает контекст сервера
func (s *Server) Context() context.Context {
	return s.ctx
}

// Close останавливает прослушивание
func (s *Server) Close() error {
	if !s.opened.Load() {
		return errors.New("ErrNotOpened")
	}
	s.cancel()
	return nil
}

// Listen запускает прослушивание сервера. Если не удалось успешно запустить прослушивание через
// заданный временной интервал (timeout), будет возвращена ошибка
func (s *Server) Listen(timeout time.Duration, ctxParent context.Context) (err error) {
	if s.opened.Swap(true) {
		return errors.New("ErrAlreadyOpened")
	}
	ctx, _ := context.WithTimeout(ctxParent, timeout)
	go func() {
		var cancel context.CancelFunc
		s.ctx, cancel = context.WithCancel(ctxParent)
		if !s.tlsEnabled {
			err = s.server.ListenAndServe()
		} else {
			err = s.server.ListenAndServeTLS(s.tlsCert, s.tlsKey)
		}
		s.opened.Store(false)
		cancel()
	}()
	<-ctx.Done()
	if err == nil {
		go func() {
			<-s.ctx.Done()
			s.server.Close()
		}()
	}
	return
}

// TokenGenerate - функция генерации токена авторизации
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) TokenParse(tokenString string) (json.Map, error) {
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return s.Secret(), nil
	})
	if err != nil {
		return nil, err
	}
	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		return json.Map(claims), nil
	}
	return nil, errors.New("invalid token")
}

// Addr возвращеат адрес, который сервер слушает
func (s *Server) Addr() string {
	return s.server.Addr
}

// Secret возвращает секретный ключ, при помощи которого генерируются токены авторизации пользователей
func (s *Server) Secret() []byte {
	return s.secret
}

// HandleFunc устанавливает функцию обработчик, которая будет вызвана по обращению к URL, удовлетворяющему pattern
func (s *Server) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
	s.server.Handler.(*http.ServeMux).HandleFunc(pattern, handler)
}