package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
	"go.uber.org/fx"
	"gopkg.in/yaml.v3"

	"iop/apps/node/internal/bootstrap"
	"iop/apps/node/internal/workspace"
	"iop/packages/go/config"
	"iop/packages/go/hostsetup"
	"iop/packages/go/version"
)

var cfgFile string

func main() {
	if handled, exitCode := workspace.RunCommandShim(os.Args); handled {
		os.Exit(exitCode)
	}
	if err := rootCmd().Execute(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

func rootCmd() *cobra.Command {
	root := &cobra.Command{
		Use:   "node",
		Short: "IOP Node — runs provider executions on this device",
	}
	root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/node.yaml", "config file path")

	root.AddCommand(serveCmd(), versionCmd(), configCmd(), setupCmd())
	return root
}

func serveCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "serve",
		Short: "Start the IOP node (connects to edge)",
		RunE: func(cmd *cobra.Command, _ []string) error {
			cfg, err := config.Load(cfgFile)
			if err != nil {
				return fmt.Errorf("load config: %w", err)
			}

			app := fx.New(bootstrap.Module(cfg))
			app.Run()
			return nil
		},
	}
}

func versionCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "version",
		Short: "Print IOP node version",
		Run: func(cmd *cobra.Command, _ []string) {
			fmt.Fprintln(cmd.OutOrStdout(), version.Version)
		},
	}
}

func configCmd() *cobra.Command {
	cfgGroup := &cobra.Command{
		Use:   "config",
		Short: "Config management commands",
	}
	cfgGroup.AddCommand(configPrintCmd(), configCheckCmd())
	return cfgGroup
}

func configPrintCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "print",
		Short: "Print the resolved configuration",
		RunE: func(cmd *cobra.Command, _ []string) error {
			cfg, err := config.Load(cfgFile)
			if err != nil {
				return fmt.Errorf("load config: %w", err)
			}
			enc := yaml.NewEncoder(cmd.OutOrStdout())
			enc.SetIndent(2)
			return enc.Encode(cfg)
		},
	}
}

func configCheckCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "check",
		Short: "Validate node config can be loaded",
		RunE: func(cmd *cobra.Command, _ []string) error {
			if _, err := config.Load(cfgFile); err != nil {
				return fmt.Errorf("load config: %w", err)
			}
			fmt.Fprintf(cmd.OutOrStdout(), "OK %s\n", cfgFile)
			return nil
		},
	}
}

func setupCmd() *cobra.Command {
	var opts hostsetup.SetupOptions
	c := &cobra.Command{
		Use:   "setup",
		Short: "Prepare host environment and systemd unit for iop-node",
		RunE: func(cmd *cobra.Command, _ []string) error {
			if cmd.Flags().Changed("config") {
				opts.ConfigPath = cfgFile
			} else {
				opts.ConfigPath = "/etc/iop/node.yaml"
			}
			return hostsetup.Run(cmd.Context(), hostsetup.NodeSpec(), opts, cmd.OutOrStdout())
		},
	}
	// setup --help shows /etc/iop/node.yaml as the --config default, not the dev default.
	c.SetHelpFunc(func(cmd *cobra.Command, args []string) {
		if f := cmd.Root().PersistentFlags().Lookup("config"); f != nil {
			prev := f.DefValue
			f.DefValue = "/etc/iop/node.yaml"
			defer func() { f.DefValue = prev }()
		}
		cmd.Root().HelpFunc()(cmd, args)
	})
	c.Flags().StringVar(&opts.BinaryPath, "binary", "", "path to iop-node binary (defaults to current executable)")
	c.Flags().StringVar(&opts.DataDir, "data-dir", "", "data directory (defaults to spec)")
	c.Flags().StringVar(&opts.UnitPath, "unit", "", "systemd unit path (defaults to spec)")
	c.Flags().StringVar(&opts.User, "user", "", "service user")
	c.Flags().StringVar(&opts.Group, "group", "", "service group")
	c.Flags().BoolVar(&opts.Enable, "enable", false, "enable the systemd unit")
	c.Flags().BoolVar(&opts.Start, "start", false, "start the systemd unit")
	c.Flags().BoolVar(&opts.Restart, "restart", false, "restart the systemd unit")
	c.Flags().BoolVar(&opts.DryRun, "dry-run", false, "print plan and previews without making changes")
	c.Flags().BoolVar(&opts.OverwriteConfig, "overwrite-config", false, "overwrite an existing config file")
	return c
}
