package kernel

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/ilyakaznacheev/cleanenv"
	_ "go.uber.org/automaxprocs"
	"golang.org/x/sync/errgroup"
)

type App interface {
	Start(context.Context) error
	OnStop(context.Context)
	GetLogLevel() LogLevel
}

type AppConfig struct {
	LogLevel LogLevel    `yaml:"log_level" env:"BH_LOG_LEVEL" env-default:"ERROR" yaml-default:"ERROR"`
	Config   interface{} `yaml:"service" env:"BH_SERVICE"`
}

func Run(parent context.Context, app App) {
	SetLogLevel(app.GetLogLevel())
	ctx, canceller := context.WithCancel(parent)
	defer canceller()

	go func() {
		defer canceller()
		InfoLog.Println("Starting service 🚀")

		if err := loadConfig(app); err != nil {
			ErrorLog.Println(err)
			return
		}

		if err := app.Start(ctx); err != nil {
			ErrorLog.Println(err)
			return
		}
	}()

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGTERM, syscall.SIGHUP)

	select {
	case signal := <-sig:
		TraceLog.Printf("[SHUTDOWN TRIGGERED] got shutdown signal: %v", signal)
	case <-ctx.Done():
		TraceLog.Println("[SHUTDOWN TRIGGERED] context exited")
	}

	InfoLog.Println("Shutting down service ⛔⚠️😱")

	errs := &errgroup.Group{}

	errs.Go(CloseGRPCConns)
	errs.Go(StopHTTPServer)
	errs.Go(StopGRPCServer)

	if err := errs.Wait(); err != nil {
		ErrorLog.Printf("There was an error shutting down the application gracefully: %v", err)
	}

	stopCtx, stopCanceller := context.WithTimeout(parent, time.Second*5)
	defer stopCanceller()
	app.OnStop(stopCtx)

	TraceLog.Println("Shutdown process fully completed")
}

func loadConfig(cfg interface{}) error {
	fp := os.Getenv("BH_APP_CONFIG_PATH")

	if fp == "" {
		fp = "/config/config.yaml"
	}

	if err := cleanenv.ReadConfig(fp, cfg); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("could not read config from file: %w", err)
	}

	if err := cleanenv.ReadEnv(cfg); err != nil {
		return fmt.Errorf("could not read config from env: %w", err)
	}

	return nil
}
