// package kserv_http -- встроенный HTTP-сервер
package kserv_http

import (
	"embed"
	"fmt"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/compress"
	"github.com/gofiber/fiber/v2/middleware/filesystem"
	"github.com/gofiber/fiber/v2/middleware/monitor"

	. "gitp78su.ipnodns.ru/svi/kern/v3/kc/helpers"
	"gitp78su.ipnodns.ru/svi/kern/v3/kc/local_ctx"
	"gitp78su.ipnodns.ru/svi/kern/v3/kc/log_buf"
	"gitp78su.ipnodns.ru/svi/kern/v3/kc/safe_bool"
	"gitp78su.ipnodns.ru/svi/kern/v3/krn/kctx"
	. "gitp78su.ipnodns.ru/svi/kern/v3/krn/ktypes"
)

const (
	streamName = "kernel_server_http" // Контрольная строка для ожидателя потока
)

// kServHttp -- встроенный HTTP-сервер
type kServHttp struct {
	kCtx     IKernelCtx
	ctx      ILocalCtx
	log      ILogBuf
	strUrl   string // URL, на котором слушает HTTP-сервер
	fiberApp *fiber.App
	isWork   ISafeBool
	isEnd    ISafeBool
}

//go:embed static/*
var embedDirStatic embed.FS

var (
	kernServHttp *kServHttp
	block        sync.Mutex
)

// GetKernelServHttp -- возвращает  встроенный HTTP-сервер
func GetKernelServHttp() IKernelServerHttp {
	block.Lock()
	defer block.Unlock()
	if kernServHttp != nil {
		kernServHttp.log.Debug("GetKernelServHttp()")
		return kernServHttp
	}
	log := log_buf.NewLogBuf(log_buf.OptIsTerm(true), log_buf.OptPrefix("kServHttp"))
	log.Debug("GetKernelServHttp(): first run")
	ctx := kctx.GetKernelCtx()

	strUrl := os.Getenv("LOCAL_HTTP_URL")
	Hassert(strUrl != "", "GetKernelServHttp(): env LOCAL_HTTP_URL not set")
	strMonolit := ctx.Get("monolitName").Val().(string)
	confFiber := fiber.Config{
		ServerHeader:      strMonolit,
		UnescapePath:      true,
		ReadTimeout:       time.Second * 15,
		WriteTimeout:      time.Second * 15,
		AppName:           strMonolit,
		Network:           "tcp4",
		EnablePrintRoutes: true,
	}
	sf := &kServHttp{
		kCtx:     ctx,
		log:      log,
		ctx:      local_ctx.NewLocalCtx(ctx.Ctx()),
		strUrl:   strUrl,
		fiberApp: fiber.New(confFiber),
		isWork:   safe_bool.NewSafeBool(),
		isEnd:    safe_bool.NewSafeBool(),
	}
	sf.fiberApp.Use(compress.New(compress.Config{
		Level: compress.LevelBestCompression, // 2
	}))
	sf.fiberApp.Use("/static", filesystem.New(filesystem.Config{
		Root:       http.FS(embedDirStatic),
		PathPrefix: "static",
		Browse:     true,
		MaxAge:     3600 * 24,
	}))
	sf.fiberApp.Get("/monitor", monitor.New(monitor.Config{Title: ctx.Get("monolitName").Val().(string)}))
	res := sf.kCtx.Wg().Add(streamName)
	res.Hassert("GetKernelServHttp(): in add stream %v", streamName)
	ctx.Set("fiberApp", sf.fiberApp, "GetKernelServHttp() internal fiber app")
	kernServHttp = sf
	ctx.Set("kServHttp", kernServHttp, "kServHttp")
	return kernServHttp
}

// IsWork -- возвращает признак работы
func (sf *kServHttp) IsWork() bool {
	return sf.isWork.Get()
}

// Log -- возвращает локальный лог
func (sf *kServHttp) Log() ILogBuf {
	return sf.log
}

// Fiber -- возвращает объект веб-приложения fiber
func (sf *kServHttp) Fiber() *fiber.App {
	return sf.fiberApp
}

// Run -- запускает сервер в работу (блокирующий вызов)
func (sf *kServHttp) Run() {
	if sf.isEnd.Get() {
		return
	}
	if sf.isWork.Get() {
		return
	}
	sf.log.Debug("Run(): url='%v'", sf.strUrl)
	lstPort := strings.Split(sf.strUrl, ":")
	strPort := lstPort[len(lstPort)-1]
	strPort = strings.ReplaceAll(strPort, "/", "")
	strPort = strings.ReplaceAll(strPort, `"`, "")
	chErr := make(chan string, 2)
	fnListen := func() {
		defer close(chErr)
		err := sf.fiberApp.Listen(":" + strPort)
		chErr <- fmt.Sprint(err)
	}
	go fnListen()
	go sf.fnChErr(chErr)
	fnCheckServer := func() (err error) {
		client := &http.Client{Timeout: 5 * time.Millisecond}
		url := sf.strUrl + "monitor"
		sf.log.Debug("url=%v", url)
		var resp *http.Response
		if resp, err = client.Get(url); err == nil {
			if resp.StatusCode == http.StatusOK {
				defer resp.Body.Close()
			}
		}
		return err
	}

	for {
		time.Sleep(time.Millisecond * 10)
		err := fnCheckServer()
		if err == nil {
			break
		}
	}

	sf.isWork.Set()
	go sf.close()
}

// В отдельном потоке ждёт закрытия канала
func (sf *kServHttp) fnChErr(chErr <-chan string) {
	strErr := <-chErr
	if strErr != "<nil>" {
		err := fmt.Errorf("kServHttp.fnChErr(): in listen, err=\n\t%v", strErr)
		sf.log.Err("Run(): err=\n\t%v", err.Error())
		sf.kCtx.Cancel()
	}
}

// Ожидает окончания работы
func (sf *kServHttp) close() {
	sf.kCtx.Done()
	if !sf.isWork.Get() {
		return
	}
	sf.isWork.Reset()
	sf.isEnd.Set()
	err := sf.fiberApp.Server().Shutdown()
	Assert(err == nil, "kServHttp.close(): in close server, err=\n\t%v", err)
	sf.kCtx.Wg().Done(streamName)
	sf.log.Debug("close(): end")
}
