package app

import (
	"database/sql"
	"net/http"
	"path/filepath"

	"forge.cadoles.com/arcad/edge/pkg/bus/memory"
	appHTTP "forge.cadoles.com/arcad/edge/pkg/http"
	"forge.cadoles.com/arcad/edge/pkg/module"
	"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
	"gitlab.com/wpetit/goweb/logger"

	"forge.cadoles.com/arcad/edge/pkg/bundle"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/pkg/errors"
	"github.com/urfave/cli/v2"

	_ "modernc.org/sqlite"
)

func RunCommand() *cli.Command {
	return &cli.Command{
		Name:  "run",
		Usage: "Run the specified app bundle",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "path",
				Usage:   "use `PATH` as app bundle (zipped bundle or directory)",
				Aliases: []string{"p"},
				Value:   ".",
			},
			&cli.StringFlag{
				Name:    "address",
				Usage:   "use `ADDRESS` as http server listening address",
				Aliases: []string{"a"},
				Value:   ":8080",
			},
			&cli.StringFlag{
				Name:  "log-format",
				Usage: "use `LOG-FORMAT` ('json' or 'human')",
				Value: "human",
			},
			&cli.IntFlag{
				Name:  "log-level",
				Usage: "use `LOG-LEVEL` (0: debug -> 5: fatal)",
				Value: 0,
			},
			&cli.StringFlag{
				Name:  "storage-file",
				Usage: "use `FILE` for SQLite storage database",
				Value: "data.sqlite",
			},
		},
		Action: func(ctx *cli.Context) error {
			address := ctx.String("address")
			path := ctx.String("path")
			logFormat := ctx.String("log-format")
			logLevel := ctx.Int("log-level")
			storageFile := ctx.String("storage-file")

			logger.SetFormat(logger.Format(logFormat))
			logger.SetLevel(logger.Level(logLevel))

			cmdCtx := ctx.Context

			absPath, err := filepath.Abs(path)
			if err != nil {
				return errors.Wrapf(err, "could not resolve path '%s'", path)
			}

			logger.Info(cmdCtx, "opening app bundle", logger.F("path", absPath))

			bundle, err := bundle.FromPath(path)
			if err != nil {
				return errors.Wrapf(err, "could not open path '%s' as an app bundle", path)
			}

			mux := chi.NewMux()

			mux.Use(middleware.Logger)

			bus := memory.NewBus()

			db, err := sql.Open("sqlite", storageFile)
			if err != nil {
				return errors.Wrapf(err, "could not open database with path '%s'", storageFile)
			}

			documentStore := sqlite.NewDocumentStoreWithDB(db)
			blobStore := sqlite.NewBlobStoreWithDB(db)

			handler := appHTTP.NewHandler(
				appHTTP.WithBus(bus),
				appHTTP.WithServerModules(
					module.ContextModuleFactory(),
					module.ConsoleModuleFactory(),
					module.LifecycleModuleFactory(bus),
					module.NetModuleFactory(bus),
					module.RPCModuleFactory(bus),
					module.StoreModuleFactory(documentStore),
					module.BlobModuleFactory(bus, blobStore),
				),
			)
			if err := handler.Load(bundle); err != nil {
				return errors.Wrap(err, "could not load app bundle")
			}

			mux.Handle("/*", handler)

			logger.Info(cmdCtx, "listening", logger.F("address", address))

			if err := http.ListenAndServe(address, mux); err != nil {
				return errors.WithStack(err)
			}

			return nil
		},
	}
}