// package qfixdpl is a Micro service quantex base project
package main

import (
	"embed"
	"flag"
	"fmt"
	"log/slog"
	"os"
	"time"

	"github.com/mitchellh/panicwrap"

	"quantex.com/qfixdpl/src/app"
	"quantex.com/qfixdpl/src/app/mode"
	"quantex.com/qfixdpl/src/app/version"
	"quantex.com/qfixdpl/src/client/config"
	googlechat "quantex.com/qfixdpl/src/client/notify/google"
	"quantex.com/qfixdpl/src/client/res"
	"quantex.com/qfixdpl/src/cmd"
	"quantex.com/qfixdpl/src/cmd/example"
	"quantex.com/qfixdpl/src/cmd/service"
	"quantex.com/qfixdpl/src/common/logger"
	"quantex.com/qfixdpl/src/common/logger/tint"
	"quantex.com/qfixdpl/src/domain"
)

// Embed the entire directory.

//go:embed res
var resources embed.FS

type flagsType struct {
	runner, globalCfg, serviceCfg, logLevel, logFormat string
	debug, version                                     bool
}

func main() {
	flags := parseFlags()

	if len(os.Args) == 1 {
		flag.Usage()

		return
	}

	if flags.version {
		fmt.Printf("%s version %s", version.AppName, version.Info())

		return
	}

	cfg, err := config.Read([]string{flags.globalCfg, flags.serviceCfg})
	if err != nil {
		panic(fmt.Sprintf("Something went wrong reading the config. %s", err))
	}

	notify := googlechat.New(cfg.Notify.Google)

	exitStatus, err := panicwrap.BasicWrap(func(output string) {
		notify.SendMsg(domain.MessageChannelPanic, output, domain.MessageStatusStopper, nil)
		os.Exit(1)
	})
	if err != nil {
		// Something went wrong setting up the panic wrapper.
		panic(fmt.Sprintf("Something went wrong setting up the panic wrapper. %s", err))
	}
	// If exitStatus >= 0, then we're the parent process and the panicwrap
	// re-executed ourselves and completed. Just exit with the proper status.
	if exitStatus >= 0 {
		os.Exit(exitStatus)
	}

	handler, err := newLogHandler(flags)
	if err != nil {
		panic(err)
	}

	hook := logger.NewQuantexHandler(handler, []slog.Level{slog.LevelError, slog.LevelWarn})
	slog.SetDefault(slog.New(hook))

	slog.Info("New Run ----------------------------------------------------------------------------")

	slog.Debug(fmt.Sprintf("flags: %+v", flags))

	v := version.AppName + " version " + version.Info()
	fmt.Println(v)
	slog.Info(v)

	setDebugMode(flags.debug)

	startRunner(flags.runner, flags.globalCfg, flags.serviceCfg)
}

func parseFlags() (f flagsType) {
	fmt.Println(f.runner)
	flag.StringVar(&f.runner, "run", "", "run the specified service")

	flag.StringVar(&f.globalCfg, "global-cfg", "../global_conf.toml", "set the config global file name")

	flag.StringVar(&f.serviceCfg, "service-cfg", "conf.toml", "set the config service file name")

	const u0 = "set the logs output format: text, json, tint1 (one line), tint2 (two lines)"
	flag.StringVar(&f.logFormat, "logs-format", "json", u0)
	flag.StringVar(&f.logFormat, "f", "json", u0+" (shorthand)")

	const u1 = "set the debug mode"
	flag.BoolVar(&f.debug, "debug", false, u1)
	flag.BoolVar(&f.debug, "d", false, u1+" (shorthand)")

	const u2 = "show the program version"
	flag.BoolVar(&f.version, "version", false, u2)
	flag.BoolVar(&f.version, "v", false, u2+" (shorthand)")

	const u3 = "set the log level: debug, info, warn, error"
	flag.StringVar(&f.logLevel, "level", "info", u3)
	flag.StringVar(&f.logLevel, "l", "info", u3+" (shorthand)")

	flag.Parse()

	return f
}

func newLogHandler(flags flagsType) (slog.Handler, error) {
	logLevel, err := parseLogLevel(flags.logLevel)
	if err != nil {
		return nil, err
	}

	switch flags.logFormat {
	case "json":
		return logger.NewJSONHandler(logLevel), nil
	case "text":
		return logger.NewTextHandler(logLevel), nil
	case "tint1":
		return logger.NewTextHandlerTint(logLevel, tint.OneLine), nil
	case "tint2":
		return logger.NewTextHandlerTint(logLevel, tint.TwoLines), nil
	default:
		flag.PrintDefaults()

		return nil, fmt.Errorf("invalid log format option: \"%s\"", flags.logFormat)
	}
}

func setDebugMode(debug bool) {
	mode.Debug = debug

	var msg string

	if mode.Debug {
		res.Set(os.DirFS("./"))

		msg = fmt.Sprintf("Running %s in DEBUG mode!", version.AppName)
	} else {
		res.Set(resources)

		msg = fmt.Sprintf("Running %s!", version.AppName)
	}

	slog.Info(msg)
}

func parseLogLevel(level string) (slog.Level, error) {
	switch level {
	case "debug":
		return slog.LevelDebug, nil
	case "info":
		return slog.LevelInfo, nil
	case "warn":
		return slog.LevelWarn, nil
	case "error":
		return slog.LevelError, nil
	default:
		return 0, fmt.Errorf("invalid log level: %s", level)
	}
}

func startRunner(runner, globalCfg, serviceCfg string) {
	var fn func(cfg app.Config) error

	if runner == "" {
		runner = "service"
	}
	switch runner {
	case "service":
		fn = service.Runner
	case "async":
		fn = example.AsyncRunner
	case "external":
		fn = example.ExternalRunner
	case "tracerr":
		fn = example.TracerrRunner
	case "logger":
		fn = example.LogsRunner
	default:
		panic("Invalid runner option: \"" + runner + "\"")
	}

	if err := cmd.NewRunner(fn)(globalCfg, serviceCfg); err != nil {
		slog.Error(err.Error())
		// Time guard to allow to notify to send the message
		time.Sleep(2 * time.Second)
	}
}
