package web

import (
	"database/sql"
	"fmt"
	"log"
	"sync"
	"time"

	"dyproxy/providers/db"

	"github.com/fsnotify/fsnotify"
	"github.com/gofiber/fiber/v3"
	"github.com/gofiber/fiber/v3/middleware/logger"
	"github.com/gofiber/fiber/v3/middleware/recover"
	"github.com/rogeecn/fabfile"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var users map[string]string = map[string]string{
	"rogeecn": "xixi@0202",
}

type Config struct {
	Version string
	Path    string
	Static  string
}

var config *Config

func load(f string) error {
	viper.SetConfigFile(f)
	viper.SetConfigType("yaml")

	if err := viper.ReadInConfig(); err != nil {
		return err
	}

	if err := viper.Unmarshal(&config); err != nil {
		return err
	}

	viper.WatchConfig()
	viper.OnConfigChange(func(e fsnotify.Event) {
		if e.Op != fsnotify.Write {
			return
		}

		if err := viper.Unmarshal(&config); err != nil {
			log.Println(err)
		}
		log.Printf("config changed: %+v", config)
	})

	return nil
}

func ServeE(cmd *cobra.Command, args []string) error {
	logrus.SetLevel(logrus.WarnLevel)

	conf, err := cmd.Flags().GetString("config")
	if err != nil {
		return err
	}

	if err := load(conf); err != nil {
		return err
	}

	db, err := db.Connect(fabfile.MustFind("data.db"))
	if err != nil {
		return err
	}
	defer db.Close()

	return New(
		WithDB(db),
		WithLogger(),
		WithRecover(),
		WithRoutes(),
		WithPendingCleaner(),
	).Serve(9090)
}

type Option func(*WebServer)

func WithDB(db *sql.DB) Option {
	return func(p *WebServer) {
		p.db = db
	}
}

func WithLogger() Option {
	return func(s *WebServer) {
		s.engine.Use(logger.New())
	}
}

// WithRecover
func WithRecover() Option {
	return func(s *WebServer) {
		s.engine.Use(recover.New())
	}
}

type WebServer struct {
	db     *sql.DB
	engine *fiber.App

	local        *time.Location
	pendingItems map[int32]time.Time
	listLock     sync.RWMutex
}

func New(opts ...Option) *WebServer {
	cstSh, _ := time.LoadLocation("Asia/Shanghai")
	s := &WebServer{
		engine: fiber.New(),
		local:  cstSh,
	}

	for _, opt := range opts {
		opt(s)
	}

	return s
}

// run
func (p *WebServer) Serve(port uint) error {
	log.Printf("server start serve at: :%d", port)
	return p.engine.Listen(fmt.Sprintf(":%d", port))
}

func WithPendingCleaner() Option {
	return func(s *WebServer) {
		if s.pendingItems == nil {
			s.pendingItems = make(map[int32]time.Time)
		}

		go func() {
			for range time.NewTicker(time.Minute * 1).C {
				s.listLock.Lock()
				for k, v := range s.pendingItems {
					if time.Since(v) > time.Minute*2 {
						delete(s.pendingItems, k)
					}
				}
				s.listLock.Unlock()
			}
		}()
	}
}
