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

import (
	"fmt"
	"os"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cache"
	"github.com/gofiber/fiber/v2/middleware/compress"
	"github.com/gofiber/fiber/v2/utils"
	"github.com/gofiber/template/html/v2"

	"git.p78su.freemyip.com/svi/gostore/internal/serv_http/router_anonym"
	"git.p78su.freemyip.com/svi/gostore/pkg/types"
)

// ServHttp -- встроенный HTTP-сервер
type ServHttp struct {
	serv     types.IService
	fiberApp *fiber.App
	port     string
	store    types.IStore
	anonym   *router_anonym.RouterAnonym
}

// NewServHttp -- возвращает новый HTTP-сервер
func NewServHttp(serv types.IService) (*ServHttp, error) {
	if serv == nil {
		return nil, fmt.Errorf("NewServHttp(): IService==nil")
	}
	port := os.Getenv("STORE_HTTP_PORT")
	if port == "" {
		return nil, fmt.Errorf("NewServHttp(): env STORE_HTTP_PORT not set")
	}
	engineTmpl := html.New("./static/tmpl", ".tmpl.html")
	appConf := fiber.Config{
		Prefork:              false,
		ServerHeader:         "GoStore",
		StrictRouting:        false,
		CaseSensitive:        false,
		Immutable:            false,
		UnescapePath:         false,
		ETag:                 false,
		BodyLimit:            10_000_000,
		Concurrency:          0,
		Views:                engineTmpl,
		ViewsLayout:          "",
		PassLocalsToViews:    false,
		ReadTimeout:          time.Second * 60,
		WriteTimeout:         time.Second * 60,
		IdleTimeout:          time.Second * 10,
		ReadBufferSize:       0,
		WriteBufferSize:      0,
		CompressedFileSuffix: ".fiber.gz",
		ProxyHeader:          "",
		GETOnly:              false,
		// ErrorHandler:                 func(*fiber.Ctx, error) error { panic("not implemented") },
		DisableKeepalive:             false,
		DisableDefaultDate:           false,
		DisableDefaultContentType:    false,
		DisableHeaderNormalizing:     false,
		DisableStartupMessage:        false,
		AppName:                      "GoStore",
		StreamRequestBody:            false,
		DisablePreParseMultipartForm: false,
		ReduceMemoryUsage:            false,
		// JSONEncoder:                  func(interface{}) ([]byte, error) { panic("not implemented") },
		// JSONDecoder:                  func([]byte, interface{}) error { panic("not implemented") },
		// XMLEncoder:                   func(interface{}) ([]byte, error) { panic("not implemented") },
		Network:                 "tcp4",
		EnableTrustedProxyCheck: false,
		TrustedProxies:          []string{},
		EnableIPValidation:      false,
		EnablePrintRoutes:       true,
		// ColorScheme: fiber.Colors{
		// 	Black:   "",
		// 	Red:     "",
		// 	Green:   "",
		// 	Yellow:  "",
		// 	Blue:    "",
		// 	Magenta: "",
		// 	Cyan:    "",
		// 	White:   "",
		// 	Reset:   "",
		// },
		// RequestMethods:           []string{},
		EnableSplittingOnParsers: false,
	}

	app := fiber.New(appConf)
	app.Static("/static", "./static")
	compressConf := compress.Config{
		Level: compress.LevelBestCompression,
	}
	app.Use(compress.New(compressConf))
	cacheConf := cache.Config{
		Next:         nil,
		Expiration:   30 * time.Minute,
		CacheHeader:  "X-Cache",
		CacheControl: true,
		KeyGenerator: func(c *fiber.Ctx) string {
			return utils.CopyString(c.Path())
		},
		ExpirationGenerator:  nil,
		StoreResponseHeaders: false,
		Storage:              nil,
		MaxBytes:             0,
		Methods:              []string{fiber.MethodGet, fiber.MethodHead},
	}
	app.Use(cache.New(cacheConf))
	sf := &ServHttp{
		serv:     serv,
		fiberApp: app,
		port:     port,
		store:    serv.Store(),
	}

	var err error
	sf.anonym, err = router_anonym.NewRouterAnonym(sf)
	if err != nil {
		return nil, err
	}
	sf.fiberApp.Get("/", sf.get)
	sf.fiberApp.Post("/put/:login/:pass/:key", sf.postLogin, sf.postPut)
	sf.fiberApp.Post("/get/:login/:pass/:key", sf.postLogin, sf.postGet)
	sf.fiberApp.Post("/del/:login/:pass/:key", sf.postLogin, sf.postDel)
	sf.fiberApp.Post("/find/:login/:pass/:key", sf.postLogin, sf.postFind)

	return sf, nil
}

// Возвращает индексную страницу хранилища
func (sf *ServHttp) get(ctx *fiber.Ctx) error {
	return ctx.Render("index", fiber.Map{})
}

// Service -- возвращает сервис
func (sf *ServHttp) Service() types.IService {
	return sf.serv
}

// FiberApp -- возвращает fiber-приложение
func (sf *ServHttp) FiberApp() *fiber.App {
	return sf.fiberApp
}

type ListValRequest struct {
	Val_ []string `json:"val" form:"val"`
}

// postFind -- возвращает список ключей по префиксу
func (sf *ServHttp) postFind(ctx *fiber.Ctx) error {
	key := ctx.Params("key")
	lstKey, err := sf.store.Find(key)
	if err != nil {
		return fiber.NewError(40, "ServHttp.postFind(): in get keys by prefix from db")
	}
	return ctx.JSON(&ListValRequest{
		Val_: lstKey,
	})
}

type ValRequest struct {
	Val_ []byte `json:"val" form:"val"`
}

// postDel -- удаляет содержимое ключа
func (sf *ServHttp) postDel(ctx *fiber.Ctx) error {
	key := ctx.Params("key")
	sf.store.Del(key)
	return nil
}

// postGet -- возвращает содержимое ключа
func (sf *ServHttp) postGet(ctx *fiber.Ctx) error {
	key := ctx.Params("key")
	val, err := sf.store.Get(key)
	if err != nil {
		return fiber.NewError(30, "ServHttp.postGet(): in get val from db")
	}
	return ctx.JSON(&ValRequest{
		Val_: val,
	})
}

// postPut -- сохраняет содержимое ключа
func (sf *ServHttp) postPut(ctx *fiber.Ctx) error {
	key := ctx.Params("key")
	req := &ValRequest{}
	if err := ctx.BodyParser(req); err != nil {
		return fiber.NewError(20, "ServHttp.postPut(): bad FORM")
	}
	err := sf.store.Put(key, req.Val_)
	if err != nil {
		return fiber.NewError(21, "ServHttp.postPut(): in put val on db")
	}
	return nil
}

// Run -- запускает веб-сервер в работу
func (sf *ServHttp) Run() error {
	defer sf.serv.CancelApp()
	err := sf.fiberApp.Listen(":" + sf.port)
	if err != nil {
		return fmt.Errorf("ServHttp.Run(): in listen port(%q), err=\n\t%w", sf.port, err)
	}
	return nil
}

// Проверяет логин/пароль в запросе
func (sf *ServHttp) postLogin(ctx *fiber.Ctx) error {
	// login := ctx.Params("login")
	// if login != sf.login {
	// 	return fiber.NewError(1, "ServHttp.postLogin(): bad login/pass")
	// }
	// pass := ctx.Params("pass")
	// if pass != sf.pass {
	// 	return fiber.NewError(2, "ServHttp.postLogin(): bad login/pass")
	// }
	key := ctx.Params("key")
	if key == "" {
		return fiber.NewError(3, "ServHttp.postLogin(): key is empty")
	}
	return ctx.Next()
}
