package server

import (
	"bytes"
	"context"
	"encoding/json"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/gofiber/fiber/v2"
	"github.com/jackc/pgx/v4/pgxpool"

	discovery "git.pbiernat.dev/egommerce/go-api-pkg/consul"
	"git.pbiernat.dev/egommerce/go-api-pkg/fluentd"
)

type Server struct {
	*fiber.App
	conf      *Config
	log       *fluentd.Logger
	db        *pgxpool.Pool
	cache     *redis.Client
	discovery *discovery.Service
	name      string
	addr      string
	kvNmspc   string
}

type Headers struct {
	RequestID string `reqHeader:"x-request-id"`
}

func NewServer(conf *Config, logger *fluentd.Logger, db *pgxpool.Pool, cache *redis.Client /*, ebCh *amqp.Channel*/) *Server {
	consul, err := discovery.NewService(conf.RegistryAddr, conf.AppID, conf.AppName, conf.AppID, conf.AppDomain, conf.PathPrefix, conf.Port)
	if err != nil {
		logger.Log("Error connecting to %s: %v", conf.RegistryAddr, err)
	}

	logger.Log("Registering service with name: %s, address: %s", consul.Name, consul.Address)
	err = consul.Register()
	if err != nil {
		logger.Log("register error: %v", err)
	}

	cnf := fiber.Config{
		AppName:      conf.AppName,
		ServerHeader: conf.AppName,
		ReadTimeout:  time.Millisecond * 50,
		WriteTimeout: time.Millisecond * 50,
		IdleTimeout:  time.Millisecond * 50,
		// Prefork:      true,
	}
	s := &Server{
		fiber.New(cnf),
		conf,
		logger,
		db,
		cache,
		/*ebCh,*/
		consul,
		conf.AppName,
		conf.NetAddr,
		conf.KVNamespace,
	}

	go func(s *Server) { // Consul Catalog and KV updater
		interval := time.Second * 15
		ticker := time.NewTicker(interval)
		for range ticker.C {
			s.updateKVConfig()
		}
	}(s)

	go func(s *Server) { // Server metadata cache updater
		interval := time.Second * 5
		ticker := time.NewTicker(interval)
		for range ticker.C {
			s.cacheMetadata()
		}
	}(s)

	SetupMiddlewares(s)
	SetupRoutes(s)

	return s
}

func (s *Server) Start() {
	err := s.Listen(s.addr)
	s.log.Log("Starting error: %v", err)
}

func (s *Server) StartWithGracefulShutdown(forever chan struct{}) {
	go func() {
		sigint := make(chan os.Signal, 1)
		signal.Notify(sigint, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
		<-sigint

		if err := s.gracefulShutdown(); err != nil {
			s.log.Log("Server is not shutting down! Reason: %v", err)
		}

		close(forever)
	}()

	if err := s.Listen(s.addr); err != nil {
		s.log.Log("Server is not running! Reason: %v", err)
	}

	<-forever
}

// GetRequestID Return current requets ID - works only when fiber context are running
func (s *Server) GetRequestID(c *fiber.Ctx) (string, error) {
	var hdr = new(Headers)
	if err := c.ReqHeaderParser(hdr); err != nil {
		return "", err
	}

	return hdr.RequestID, nil
}

func (s *Server) updateKVConfig() { // FIXME: duplicated in cmd/worker/main.go
	config, _, err := s.discovery.KV().Get(s.kvNmspc, nil)
	if err != nil || config == nil {
		return
	}

	kvCnf := bytes.NewBuffer(config.Value)
	decoder := json.NewDecoder(kvCnf)
	if err := decoder.Decode(&s.conf); err != nil {
		return
	}
}

func (s *Server) cacheMetadata() {
	ctx := context.Background()
	key, address := "internal__"+s.conf.AppName+"__ips", s.conf.AppID // FIXME: key name

	pos := s.cache.LPos(ctx, key, address, redis.LPosArgs{}).Val()
	if pos >= 0 {
		s.cache.LRem(ctx, key, 0, address)
	}

	s.cache.LPush(ctx, key, address).Err()
}

func (s *Server) clearMetadataCache() {
	ctx := context.Background()
	key, address := "internal__"+s.conf.AppName+"__ips", s.conf.AppID // FIXME: key name

	s.cache.LRem(ctx, key, 0, address)
}

func (s *Server) gracefulShutdown() error {
	s.log.Log("Server is going down...")
	s.log.Log("Unregistering service: %s", s.discovery.GetID())
	s.discovery.Unregister()
	s.clearMetadataCache()

	// s.ebCh.Close()
	s.db.Close()
	s.log.Close()

	return s.Shutdown()
}
