package server

import (
	"context"
	"html/template"
	"math/rand"
	"net/http"
	"os"
	"path"
	"strings"
	"time"

	"github.com/allegro/bigcache/v3"
	"github.com/dustin/go-humanize"
	"github.com/iris-contrib/middleware/cors"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/middleware/rate"
	"github.com/rs/zerolog/log"

	"pluja.dev/kycnot.me/config"
	"pluja.dev/kycnot.me/utils/pow"
)

type Server struct {
	ListenAddr    string
	Router        *iris.Application
	PowChallenger *pow.PowChallenger
	Cache         *bigcache.BigCache
	HttpClient    *http.Client
}

func NewServer(listenAddr string) *Server {
	app := iris.New()
	cache, err := bigcache.New(context.Background(), bigcache.Config{
		Shards:             1024,
		LifeWindow:         30 * (24 * time.Hour),
		MaxEntriesInWindow: 1000 * 10 * 60,
		MaxEntrySize:       500,
		Verbose:            false,
		HardMaxCacheSize:   0,
	})
	if err != nil {
		log.Fatal().Err(err).Msg("Could not initialize cache")
	}

	return &Server{
		ListenAddr:    listenAddr,
		Router:        app,
		PowChallenger: &pow.PowChallenger{},
		Cache:         cache,
		HttpClient:    &http.Client{},
	}
}

func (s *Server) Run() error {
	s.SetupMiddleware()
	s.RegisterRoutes()
	s.RegisterViews()
	s.PowChallenger.Init()
	return s.Router.Listen(s.ListenAddr, iris.WithSitemap("https://kycnot.me"))
}

func (s *Server) SetupMiddleware() {
	s.Router.Use(iris.Compression)

	if config.Conf.Dev {
		log.Debug().Msg("CORS is enabled")
		crs := cors.New(cors.Options{
			AllowedOrigins:   []string{"*"}, // allows everything, use that to change the hosts.
			AllowCredentials: true,
		})
		s.Router.Use(crs)
	} else {
		crs := cors.New(cors.Options{
			AllowedOrigins:   []string{"*"}, // allows everything, use that to change the hosts.
			AllowCredentials: true,
		})
		s.Router.Use(crs)
	}

	if config.Conf.Dev {
		s.Router.Logger().SetLevel("debug")
		s.Router.Use(iris.Cache304(10 * time.Second))
	} else {
		s.Router.Logger().SetLevel("warn")
		s.Router.Use(iris.Cache304(24 * 60 * 60))
	}
}

func (s *Server) RegisterRoutes() {
	// Static routes
	s.Router.Favicon(path.Join(os.Getenv("ROOT_DIR"), "/frontend/static", "/assets/favicon.webp"))
	s.Router.HandleDir("/static", iris.Dir(path.Join(os.Getenv("ROOT_DIR"), "/frontend/static")))

	// GET
	s.Router.Get("/", s.handleIndex)
	s.Router.Get("/about", s.handleAbout)
	s.Router.Get("/pending", s.handlePending)
	s.Router.Get("/service/{name:string}", s.handleService)
	s.Router.Get("/exchange/{name:string}", s.handleService)
	s.Router.Get("/point/{id:string}", s.handleAttribute)
	s.Router.Get("/attr/{id:string}", s.handleAttribute)
	s.Router.Get("/request/service", s.handleRequestServiceForm)

	// Miscellaneous Routes
	s.Router.Get("/pgp", s.handlePgp)
	s.Router.Get("/nostr", s.handleNostr)

	// POST
	s.Router.Post("/new/service", s.handlePostRequestServiceForm)

	// API routes
	genericApi := s.Router.Party("/api/g")
	{
		genericApi.Get("/pow/verify/{id}/{nonce:int}", s.handleVerifyPow)
		genericApi.Get("/picture/{id}", s.handleApiPicture)
	}

	v1Api := s.Router.Party("/api/v1")
	{
		limitV1 := rate.Limit(rate.Every(1*time.Minute), 5, rate.PurgeEvery(time.Minute, 5*time.Minute))
		v1Api.Use(limitV1)
		v1Api.Get("/service/{name:string}", s.handleApiService)
		v1Api.Get("/service/{name:string}/summary", s.handleScoreSummary)
	}
}

func (s *Server) RegisterViews() {
	// Use blocks as the templating engine
	blocks := iris.Blocks("./frontend/templates", ".html")
	if config.Conf.Dev {
		blocks.Reload(true)
	}
	blocks.Engine.Funcs(template.FuncMap{
		"attr": func(s string) template.HTMLAttr {
			return template.HTMLAttr(s)
		},
		"safe": func(s string) template.HTML {
			return template.HTML(s)
		},
		"randomElem": func(vs []string) string {
			if len(vs) == 0 {
				return ""
			}
			return vs[rand.Intn(len(vs))]
		},
		"shortText": func(s string) string {
			if len(s) > 50 {
				return strings.TrimSpace(s[:50]) + "..."
			}
			return s
		},
		"humanizePbTimeString": func(t string) string {
			if t == "" {
				return "Unknown"
			}

			layout := "2006-01-02 15:04:05.000Z"

			tm, err := time.Parse(layout, t)
			if err != nil {
				return t
			}
			return humanize.Time(tm)
		},
		"dateString": func(t string) string {
			if t == "" {
				return "Unknown"
			}

			layout := "2006-01-02 15:04:05.000Z"

			tm, err := time.Parse(layout, t)
			if err != nil {
				return t
			}
			return tm.Format("2006-01-02")
		},
		"isNew": func(t string) bool {
			if t == "" {
				return false
			}

			layout := "2006-01-02 15:04:05.000Z"

			tm, err := time.Parse(layout, t)
			if err != nil {
				return false
			}
			return time.Since(tm) < 7*(24*time.Hour)
		},
	})
	s.Router.RegisterView(blocks)
}
