// Command dev is the AgentTeam local development orchestrator.
//
// It supports three modes, all of which stay attached to the terminal and
// shut down cleanly on Ctrl+C:
//
//	dev --server     # build & run unified agentteam-server + Vite, rebuild on .go changes
//	dev --desktop    # build & run the Electron Desktop development stack
//	dev --database   # launch a local TiDB playground until cancelled
package main

import (
	"context"
	"errors"
	"log/slog"
	"os"

	"github.com/spf13/cobra"

	"github.com/tidbcloud/AgentTeam/dev/internal/config"
	"github.com/tidbcloud/AgentTeam/dev/internal/database"
	"github.com/tidbcloud/AgentTeam/dev/internal/deps"
	"github.com/tidbcloud/AgentTeam/dev/internal/desktop"
	"github.com/tidbcloud/AgentTeam/dev/internal/env"
	"github.com/tidbcloud/AgentTeam/dev/internal/features"
	"github.com/tidbcloud/AgentTeam/dev/internal/server"
	"github.com/tidbcloud/AgentTeam/dev/internal/tidb"
	"github.com/tidbcloud/AgentTeam/x/proc"
)

func main() {
	slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
		ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
			if a.Key == slog.TimeKey {
				return slog.String(a.Key, a.Value.Time().Format("15:04:05"))
			}
			return a
		},
	})).With(slog.String("component", "dev")))

	if err := newRootCommand().Execute(); err != nil {
		slog.Error(err.Error())
		os.Exit(1)
	}
}

func newRootCommand() *cobra.Command {
	var (
		serverMode        bool
		desktopMode       bool
		databaseMode      bool
		databaseStartMode bool
	)
	cmd := &cobra.Command{
		Use:           "dev (--server | --desktop | --database | --database-start)",
		Short:         "Run AgentTeam local development helpers",
		Long:          "Run AgentTeam local development helpers.\n\nModes (pick exactly one):\n  --server          build & run unified agentteam-server + Vite, rebuild on .go changes\n  --desktop         build the Go sidecar and Electron main/preload, then run Vite + Electron\n  --database        launch a local TiDB playground until Ctrl+C\n  --database-start  launch a durable local TiDB playground and return when ready\n\nTiDB playground options (--database and --database-start) are configured via env vars:\n  AGENTTEAM_DEV_TIDB_TAG         tiup --tag (default: derived from RETAIN_DATA)\n  AGENTTEAM_DEV_TIDB_DB_PORT     TiDB DB port (default: 4000)\n  AGENTTEAM_DEV_TIDB_PD_PORT     TiDB PD port (default: 2379)\n  AGENTTEAM_DEV_TIDB_KV_PORT     TiDB KV port (default: 20160)\n  AGENTTEAM_DEV_TIDB_VERSION     TiDB version (default: v8.5.5)\n  AGENTTEAM_DEV_TIDB_DATABASE    Database to auto-create (default: agentteam_core)\n\nAttached modes shut down cleanly on Ctrl+C.",
		SilenceErrors: true,
		SilenceUsage:  true,
		Args: func(cmd *cobra.Command, args []string) error {
			if err := cobra.NoArgs(cmd, args); err != nil {
				return err
			}
			count := 0
			for _, b := range []bool{serverMode, desktopMode, databaseMode, databaseStartMode} {
				if b {
					count++
				}
			}
			if count != 1 {
				return errors.New("select exactly one mode: --server, --desktop, --database, or --database-start")
			}
			return nil
		},
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			repoRoot, err := env.FindRepoRoot()
			if err != nil {
				return err
			}
			env.Load(repoRoot)
			return nil
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			repoRoot, err := env.FindRepoRoot()
			if err != nil {
				return err
			}
			switch {
			case databaseStartMode:
				return runDatabaseStart(cmd.Context(), repoRoot)
			case databaseMode:
				return runDatabase(cmd.Context(), repoRoot)
			case desktopMode:
				return runDesktop(cmd.Context(), repoRoot)
			default:
				return runServer(cmd.Context(), repoRoot)
			}
		},
	}
	cmd.Flags().BoolVar(&serverMode, "server", false, "run AgentTeam server and Web development stack")
	cmd.Flags().BoolVar(&desktopMode, "desktop", false, "run the Electron Desktop development stack")
	cmd.Flags().BoolVar(&databaseMode, "database", false, "run a local TiDB playground until Ctrl+C")
	cmd.Flags().BoolVar(&databaseStartMode, "database-start", false, "start a detached local TiDB playground and return when ready")
	return cmd
}

func runServer(parent context.Context, repoRoot string) error {
	opts, err := features.Load(repoRoot)
	if err != nil {
		return err
	}
	if err := features.ApplyEnvironment(opts); err != nil {
		return err
	}
	cfg := config.Build()

	mgr := proc.New(os.Stderr)

	var depsProc *deps.Processes
	var srv *server.Runner
	err = proc.RunWithSignalsAndForceExit(parent, func(ctx context.Context) error {
		slog.Info("== dependencies ==")
		depsProc, err = deps.Prepare(ctx, repoRoot, opts, cfg)
		if err != nil {
			return err
		}
		srv = server.New(repoRoot, opts, cfg, mgr)
		return srv.Run(ctx)
	}, func() {
		slog.Info("== cleanup ==")
		if srv != nil {
			srv.StopAll()
		}
		if depsProc != nil {
			depsProc.Stop()
		}
		mgr.SweepRepo(repoRoot)
	}, func() {
		slog.Info("== force kill ==")
		mgr.ForceKillAll()
		mgr.SweepRepo(repoRoot)
	})
	return err
}

func runDesktop(parent context.Context, repoRoot string) error {
	mgr := proc.New(os.Stderr)
	r := desktop.New(repoRoot, mgr)
	err := proc.RunWithSignalsAndForceExit(parent, func(ctx context.Context) error {
		return r.Run(ctx)
	}, func() {
		slog.Info("== cleanup ==")
		r.Stop()
	}, func() {
		slog.Info("== force kill ==")
		r.ForceKill()
	})
	return err
}

func runDatabase(parent context.Context, repoRoot string) error {
	opts := database.Resolve(database.Options{RepoRoot: repoRoot})
	return proc.RunWithSignals(parent, func(ctx context.Context) error {
		return database.Run(ctx, opts)
	}, func() {
		slog.Info("== cleanup ==")
		if opts.Tag != "" {
			tidb.StopPlayground(repoRoot, opts.Tag)
		}
	})
}

func runDatabaseStart(ctx context.Context, repoRoot string) error {
	opts := database.Resolve(database.Options{RepoRoot: repoRoot})
	return database.Start(ctx, opts)
}
