package main

import (
	"context"
	"embed"
	"fmt"
	corelog "log"
	"log/slog"
	_ "net/http/pprof"
	"os"
	"runtime/debug"

	"git.sr.ht/~ashkeel/strimertul/log"

	"git.sr.ht/~ashkeel/strimertul/utils"
	"github.com/apenwarr/fixconsole"
	"github.com/urfave/cli/v2"
	"github.com/wailsapp/wails/v2"
	"github.com/wailsapp/wails/v2/pkg/options"
	"github.com/wailsapp/wails/v2/pkg/options/assetserver"
	"github.com/wailsapp/wails/v2/pkg/runtime"
)

const devVersionMarker = "v0.0.0-UNKNOWN"

var appVersion = devVersionMarker

const (
	crashReportURL = "https://crash.strimertul.stream/upload"
)

//go:embed frontend/dist
var frontend embed.FS

func main() {
	if err := fixconsole.FixConsoleIfNeeded(); err != nil {
		corelog.Fatal(err)
	}

	var panicLog *os.File

	app := &cli.App{
		Name:    "strimertul",
		Usage:   "the small broadcasting suite for Twitch",
		Version: appVersion,
		Action:  cliMain,
		Flags: []cli.Flag{
			&cli.StringFlag{Name: "log-level", Usage: "logging level (debug,info,warn,error)", Value: "info"},
			&cli.StringFlag{Name: "driver", Usage: "specify database driver", Value: "auto"},
			&cli.StringFlag{Name: "database-dir", Aliases: []string{"db-dir"}, Usage: "specify database directory", Value: "data"},
			&cli.StringFlag{Name: "backup-dir", Aliases: []string{"b-dir"}, Usage: "specify backup directory", Value: "backups"},
			&cli.IntFlag{Name: "backup-interval", Aliases: []string{"b-i"}, Usage: "specify backup interval (in minutes, 0 to disable)", Value: 60},
			&cli.IntFlag{Name: "max-backups", Aliases: []string{"b-max"}, Usage: "maximum number of backups to keep, older ones will be deleted, set to 0 to keep all", Value: 20},
		},
		Commands: []*cli.Command{
			{
				Name:      "import",
				Usage:     "import database from JSON file",
				ArgsUsage: "[-f input.json]",
				Flags: []cli.Flag{
					&cli.StringFlag{Name: "file", Aliases: []string{"f"}, Usage: "file to open", DefaultText: "STDIN"},
				},
				Action: cliImport,
			},
			{
				Name:      "export",
				Usage:     "export database as JSON file",
				ArgsUsage: "[-f output.json]",
				Flags: []cli.Flag{
					&cli.StringFlag{Name: "file", Aliases: []string{"f"}, Usage: "file to save to", DefaultText: "STDOUT"},
				},
				Action: cliExport,
			},
			{
				Name:      "restore",
				Usage:     "restore database from backup",
				ArgsUsage: "[-f backup.db]",
				Flags: []cli.Flag{
					&cli.StringFlag{Name: "file", Aliases: []string{"f"}, Usage: "backup to open", DefaultText: "STDOUT"},
				},
				Action: cliRestore,
			},
		},
		Before: func(ctx *cli.Context) error {
			// Initialize logger with global flags
			level := slog.LevelInfo

			if err := level.UnmarshalText([]byte(ctx.String("log-level"))); err != nil {
				return cli.Exit(fmt.Sprintf("Invalid log level: %s", err), 1)
			}
			log.Init(level)

			// Create file for panics
			var err error
			panicLog, err = os.OpenFile(log.PanicFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o666)
			if err != nil {
				slog.Warn("Could not create panic log", log.Error(err))
			} else {
				utils.RedirectStderr(panicLog)
			}

			// For development builds, force crash dumps
			if isDev() {
				debug.SetTraceback("crash")
			}

			return nil
		},
		After: func(_ *cli.Context) error {
			if panicLog != nil {
				utils.Close(panicLog)
			}

			return nil
		},
	}

	if err := app.Run(os.Args); err != nil {
		corelog.Fatal(err)
	}
}

func cliMain(ctx *cli.Context) error {
	// Create an instance of the app structure
	app := NewApp(ctx)

	// Create application with options
	err := wails.Run(&options.App{
		Title:     "strimertul",
		Width:     1024,
		Height:    768,
		MinWidth:  480,
		MinHeight: 300,
		AssetServer: &assetserver.Options{
			Assets: frontend,
		},
		SingleInstanceLock: &options.SingleInstanceLock{
			UniqueId:               "d1272ae3-765a-4768-97cb-3203b788e7c5",
			OnSecondInstanceLaunch: app.onSecondInstanceLaunch,
		},
		EnableDefaultContextMenu: true,
		BackgroundColour:         &options.RGBA{R: 27, G: 38, B: 54, A: 1},
		OnStartup:                app.startup,
		OnShutdown:               app.stop,
		OnBeforeClose: func(ctx context.Context) (prevent bool) {
			dialog, err := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
				Type:    runtime.QuestionDialog,
				Title:   "Quit?",
				Message: "Are you sure you want to quit?",
			})
			if err != nil {
				return false
			}
			return dialog != "Yes"
		},
		Bind: []any{
			app,
		},
	})
	if err != nil {
		return cli.Exit(fmt.Errorf("%s: %w", "App exited unexpectedly", err), 1)
	}
	return nil
}

func warnOnError(err error, text string, fields ...any) {
	if err != nil {
		fields = append(fields, log.Error(err))
		slog.Warn(text, fields...)
	}
}

func fatalError(err error, text string) error {
	return cli.Exit(fmt.Errorf("%s: %w", text, err), 1)
}

// isDev checks if the running code is a development version
func isDev() bool {
	return appVersion == devVersionMarker
}
