package server

import (
	"log"
	"net"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"time"

	"github.com/gofiber/fiber/v2"

	"git.pbiernat.dev/egommerce/api-entities/http"
)

type (
	Server struct {
		*fiber.App
		*Config

		addr string // e.g. "127.0.0.1:8080"

		PurgeFn
	}
	HeaderRequestID struct {
		RequestID string `reqHeader:"x-request-id"`
	}
	OptionFn func(*Server) error
	PurgeFn  func() error
)

func New(conf *Config) *Server {
	return &Server{
		App: fiber.New(fiber.Config{
			AppName:      conf.AppID,
			ServerHeader: conf.AppName + ":" + conf.AppID,
			ReadTimeout:  conf.ReadTimeout * time.Millisecond,
			WriteTimeout: conf.WriteTimeout * time.Millisecond,
			IdleTimeout:  conf.IdleTimeout * time.Millisecond,
		}),
		Config: conf,
		addr:   conf.NetAddr,
	}
}

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

		if err := s.PurgeFn(); err != nil {
			log.Fatalf("Failed to shutdown server. Reason: %v\n", err)
		}

		close(while)
	}()

	run := s.createRunFile("/app.run") // TODO move to common library (shared between server and worker)
	defer s.removeRunFile(run)

	ln, _ := net.Listen("tcp", s.addr)
	// ln = tls.NewListener(ln, s.App.Server().TLSConfig)
	err := s.Listener(ln)
	if err != nil {
		log.Fatalf("Failed to start server: %s. Reason: %v\n", s.Config.AppID, err)
		close(while)
	}
	<-while

	return err
}

func (s *Server) GetRequestID(c *fiber.Ctx) (string, error) {
	var hdr = new(HeaderRequestID)
	if err := c.ReqHeaderParser(hdr); err != nil {
		return "", err
	}

	return hdr.RequestID, nil
}

func (s *Server) Error(c *fiber.Ctx, code int, msg string) error {
	return c.Status(code).JSON(http.ErrorResponse{Error: msg})
}

func (s *Server) createRunFile(path string) *os.File {
	run, err := os.Create(path)
	if err != nil {
		log.Fatalf("Failed to create run file. Reason: %v\n", err)
		os.Exit(1)
	}
	run.WriteString(strconv.Itoa(os.Getpid()))

	return run
}

func (s *Server) removeRunFile(f *os.File) error {
	return f.Close()
}
