package server

import (
	"errors"
	"os"
	"path/filepath"

	"coopcloud.tech/abra/cli/internal"
	"coopcloud.tech/abra/pkg/client"
	"coopcloud.tech/abra/pkg/config"
	contextPkg "coopcloud.tech/abra/pkg/context"
	"coopcloud.tech/abra/pkg/dns"
	"coopcloud.tech/abra/pkg/server"
	sshPkg "coopcloud.tech/abra/pkg/ssh"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

var local bool
var localFlag = &cli.BoolFlag{
	Name:        "local, l",
	Usage:       "Use local server",
	Destination: &local,
}

func cleanUp(domainName string) {
	if domainName != "default" {
		logrus.Infof("cleaning up context for %s", domainName)
		if err := client.DeleteContext(domainName); err != nil {
			logrus.Fatal(err)
		}
	}

	logrus.Infof("attempting to clean up server directory for %s", domainName)

	serverDir := filepath.Join(config.SERVERS_DIR, domainName)
	files, err := config.GetAllFilesInDirectory(serverDir)
	if err != nil {
		logrus.Fatalf("unable to list files in %s: %s", serverDir, err)
	}

	if len(files) > 0 {
		logrus.Warnf("aborting clean up of %s because it is not empty", serverDir)
		return
	}

	if err := os.RemoveAll(serverDir); err != nil {
		logrus.Fatal(err)
	}
}

// newContext creates a new internal Docker context for a server. This is how
// Docker manages SSH connection details. These are stored to disk in
// ~/.docker. Abra can manage this completely for the user, so it's an
// implementation detail.
func newContext(c *cli.Context, domainName, username, port string) error {
	store := contextPkg.NewDefaultDockerContextStore()
	contexts, err := store.Store.List()
	if err != nil {
		return err
	}

	for _, context := range contexts {
		if context.Name == domainName {
			logrus.Debugf("context for %s already exists", domainName)
			return nil
		}
	}

	logrus.Debugf("creating context with domain %s, username %s and port %s", domainName, username, port)

	if err := client.CreateContext(domainName, username, port); err != nil {
		return err
	}

	return nil
}

// createServerDir creates the ~/.abra/servers/... directory for a new server.
func createServerDir(domainName string) error {
	if err := server.CreateServerDir(domainName); err != nil {
		if !os.IsExist(err) {
			return err
		}
		logrus.Debugf("server dir for %s already created", domainName)
	}

	return nil
}

var serverAddCommand = cli.Command{
	Name:    "add",
	Aliases: []string{"a"},
	Usage:   "Add a server to your configuration",
	Description: `
Add a new server to your configuration so that it can be managed by Abra.

Abra uses the SSH command-line to discover connection details for your server.
It is advised to configure an entry per-host in your ~/.ssh/config for each
server. For example:

Host example.com
  Hostname example.com
  User exampleUser
  Port 12345
  IdentityFile ~/.ssh/example@somewhere

Abra can then load SSH connection details from this configuratiion with:

    abra server add example.com

If "--local" is passed, then Abra assumes that the current local server is
intended as the target server. This is useful when you want to have your entire
Co-op Cloud config located on the server itself, and not on your local
developer machine.
`,
	Flags: []cli.Flag{
		internal.DebugFlag,
		internal.NoInputFlag,
		localFlag,
	},
	Before:    internal.SubCommandBefore,
	ArgsUsage: "<domain>",
	Action: func(c *cli.Context) error {
		if len(c.Args()) > 0 && local || !internal.ValidateSubCmdFlags(c) {
			err := errors.New("cannot use <domain> and --local together")
			internal.ShowSubcommandHelpAndError(c, err)
		}

		var domainName string
		if local {
			domainName = "default"
		} else {
			domainName = internal.ValidateDomain(c)
		}

		if local {
			if err := createServerDir(domainName); err != nil {
				logrus.Fatal(err)
			}

			logrus.Infof("attempting to create client for %s", domainName)
			if _, err := client.New(domainName); err != nil {
				cleanUp(domainName)
				logrus.Fatal(err)
			}

			logrus.Info("local server added")

			return nil
		}

		if _, err := dns.EnsureIPv4(domainName); err != nil {
			logrus.Fatal(err)
		}

		if err := createServerDir(domainName); err != nil {
			logrus.Fatal(err)
		}

		hostConfig, err := sshPkg.GetHostConfig(domainName)
		if err != nil {
			logrus.Fatal(err)
		}

		if err := newContext(c, domainName, hostConfig.User, hostConfig.Port); err != nil {
			logrus.Fatal(err)
		}

		logrus.Infof("attempting to create client for %s", domainName)
		if _, err := client.New(domainName); err != nil {
			cleanUp(domainName)
			logrus.Debugf("failed to construct client for %s, saw %s", domainName, err.Error())
			logrus.Fatal(sshPkg.Fatal(domainName, err))
		}

		logrus.Infof("%s added", domainName)

		return nil
	},
}