// Copyright (C) 2023 Marius Schellenberger

package api

import (
	"context"
	"net"
	"net/http"
	"os"
	"time"

	"git.giftfish.de/ston1th/godrop/v2"
	"git.giftfish.de/ston1th/keyctl/pkg/api/types"
	serverv1 "git.giftfish.de/ston1th/keyctl/pkg/api/v1/server"
	"git.giftfish.de/ston1th/keyctl/pkg/db"

	"github.com/go-logr/logr"
	"github.com/gorilla/mux"
)

// Server is the webapp and api server
type Server struct {
	srv *http.Server
	mux *mux.Router
	log logr.Logger

	Data *types.ContextData

	listen net.Listener
	socket net.Listener
}

type notFoundHandler struct {
	s *Server
}

func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	types.NewContext(w, r, nil, nf.s.log).Err(types.ErrInvalidAPIRoute)
}

// NewHTTPServer returns a new HTTPServer
func NewServer(log logr.Logger, listen, socket string, db *db.DB) (*Server, error) {
	m := mux.NewRouter()
	s := &Server{
		mux: m,
		log: log,
		Data: &types.ContextData{
			Approver: types.NewApprover(),
			DB:       db,
		},
		srv: &http.Server{
			Handler:      m,
			ReadTimeout:  10 * time.Second,
			WriteTimeout: 10 * time.Second,
		},
	}
	list, err := net.Listen("tcp", listen)
	if err != nil {
		return nil, err
	}
	s.listen = list

	sock, err := unixListener(socket)
	if err != nil {
		return nil, err
	}
	s.socket = sock

	m.NotFoundHandler = &notFoundHandler{s}
	for _, v := range serverv1.Routes {
		m.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
	}

	go func() {
		err := s.srv.Serve(s.listen)
		if err != nil && err != http.ErrServerClosed {
			s.log.Error(err, "")
		}
	}()
	go func() {
		err := s.srv.Serve(s.socket)
		if err != nil && err != http.ErrServerClosed {
			s.log.Error(err, "")
		}
	}()
	return s, nil
}

func unixListener(socket string) (sock net.Listener, err error) {
	err = godrop.Unveil(socket, "rwc")
	if err != nil {
		return
	}
	err = godrop.UnveilBlock()
	if err != nil {
		return
	}
	sock, err = net.Listen("unix", socket)
	if err != nil {
		return
	}
	err = os.Chmod(socket, 0o660)
	return
}

func (s *Server) contextWrapper(h types.CtxHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		h(types.NewContext(w, r, s.Data, s.log))
	}
}

// Stop stops listening for incoming connections and closes currently open connections
func (s *Server) Shutdown(ctx context.Context) {
	s.log.Info("stopping")
	err := s.srv.Shutdown(ctx)
	if err != nil {
		s.log.Error(err, "")
	}
	s.log.Info("stopped")
}
