package main

import (
	"context"
	"entgo.io/ent/dialect"
	"entgo.io/ent/dialect/sql"
	"flag"
	"fmt"
	"github.com/coreos/go-systemd/v22/activation"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/go-chi/cors"
	"github.com/go-chi/render"
	_ "github.com/jackc/pgx/v4/stdlib"
	log "github.com/sirupsen/logrus"
	"github.com/wercker/journalhook"
	"gopkg.in/yaml.v3"
	"net"
	"net/http"
	"os"
	"os/signal"
	"somegit.dev/ALHP/ALHP.GO/ent"
	"syscall"
	"time"
)

var (
	conf       *Conf
	repos      []string
	db         *ent.Client
	journalLog = flag.Bool("journal", false, "Log to systemd journal instead of stdout")
	sqlDebug   = flag.Bool("sqldebug", false, "Enable SQL debug log")
	configFile = flag.String("config", "config.yaml", "set config file name/path")
)

func main() {
	killSignals := make(chan os.Signal, 1)
	signal.Notify(killSignals, syscall.SIGINT, syscall.SIGTERM)

	reloadSignals := make(chan os.Signal, 1)
	signal.Notify(reloadSignals, syscall.SIGUSR1)

	flag.Parse()

	confStr, err := os.ReadFile(*configFile)
	if err != nil {
		log.Fatalf("error reading config file: %v", err)
	}

	err = yaml.Unmarshal(confStr, &conf)
	if err != nil {
		log.Fatalf("error parsing config file: %v", err)
	}

	lvl, err := log.ParseLevel(conf.Logging.Level)
	if err != nil {
		log.Fatalf("error parsing log level from config: %v", err)
	}
	log.SetLevel(lvl)
	if *journalLog {
		journalhook.Enable()
	}

	if conf.DB.Driver == "pgx" {
		pdb, err := sql.Open("pgx", conf.DB.ConnectTo)
		if err != nil {
			log.Fatalf("failed to open database %s: %v", conf.DB.ConnectTo, err)
		}

		drv := sql.OpenDB(dialect.Postgres, pdb.DB())
		db = ent.NewClient(ent.Driver(drv))
	} else {
		db, err = ent.Open(conf.DB.Driver, conf.DB.ConnectTo)
		if err != nil {
			log.Panicf("failed to open database %s: %v", conf.DB.ConnectTo, err)
		}
		defer func(Client *ent.Client) {
			_ = Client.Close()
		}(db)
	}

	if *sqlDebug {
		db = db.Debug()
	}

	if err := db.Schema.Create(context.Background()); err != nil {
		log.Panicf("automigrate failed: %v", err)
	}

	r := chi.NewRouter()
	r.Use(middleware.RequestID)
	r.Use(middleware.RealIP)
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)
	r.Use(middleware.Timeout(60 * time.Second))
	r.Use(render.SetContentType(render.ContentTypeJSON))
	r.Use(cors.AllowAll().Handler)

	// routes
	r.Get("/stats", GetStats)
	r.Get("/packages", GetPackages)

	log.Info("Start listening...")
	sockets := make([]net.Listener, 0)

	// handle systemd socket activation
	listeners, err := activation.Listeners()
	if err != nil {
		panic(err)
	}

	if len(listeners) > 0 {
		for _, l := range listeners {
			log.Infof("listening on %s", l.Addr())
			if err != nil {
				log.Panicf("failure listing on socket %s: %v", l, err)
			}
			sockets = append(sockets, l)
			l := l
			go func() {
				srv := &http.Server{
					ReadTimeout:  time.Duration(conf.Httpd.Timeout.Read) * time.Second,
					WriteTimeout: time.Duration(conf.Httpd.Timeout.Write) * time.Second,
					IdleTimeout:  time.Duration(conf.Httpd.Timeout.Idle) * time.Second,
					Handler:      r,
				}
				_ = srv.Serve(l)
			}()
		}
	} else { // handle config if not systemd-socket activated
		for _, l := range conf.Httpd.Listen {
			if l.Socket != "" {
				sL, err := net.Listen("unix", l.Socket)
				log.Infof("Listening on %s", l.Socket)
				if err != nil {
					log.Panicf("Failure listing on socket %s: %v", l.Socket, err)
				}
				sockets = append(sockets, sL)
				go func() {
					srv := &http.Server{
						ReadTimeout:  time.Duration(conf.Httpd.Timeout.Read) * time.Second,
						WriteTimeout: time.Duration(conf.Httpd.Timeout.Write) * time.Second,
						IdleTimeout:  time.Duration(conf.Httpd.Timeout.Idle) * time.Second,
						Handler:      r,
					}
					_ = srv.Serve(sL)
				}()
			} else {
				log.Infof("Listening on %s:%d", l.Host, l.Port)
				tL, err := net.Listen("tcp", fmt.Sprintf("%s:%d", l.Host, l.Port))
				if err != nil {
					log.Panicf("Failure listing on %s:%d: %v", l.Host, l.Port, err)
				}
				go func(l struct {
					Socket string
					Host   string
					Port   int
				}) {
					srv := &http.Server{
						ReadTimeout:  time.Duration(conf.Httpd.Timeout.Read) * time.Second,
						WriteTimeout: time.Duration(conf.Httpd.Timeout.Write) * time.Second,
						IdleTimeout:  time.Duration(conf.Httpd.Timeout.Idle) * time.Second,
						Handler:      r,
					}
					err = srv.Serve(tL)
					if err != nil {
						log.Fatalf("Failure serving on %s:%d: %v", l.Host, l.Port, err)
					}
				}(l)
			}
		}
	}

killLoop:
	for {
		select {
		case <-killSignals:
			break killLoop
		case <-reloadSignals:
			confStr, err := os.ReadFile(*configFile)
			if err != nil {
				log.Panicf("unable to open config: %v", err)
			}

			err = yaml.Unmarshal(confStr, &conf)
			if err != nil {
				log.Panicf("unable to parse config: %v", err)
			}

			lvl, err := log.ParseLevel(conf.Logging.Level)
			if err != nil {
				log.Panicf("failure setting logging level: %v", err)
			}
			log.SetLevel(lvl)
			log.Infof("config reloaded")
		}
	}

	for _, s := range sockets {
		_ = s.Close()
	}
}
