package app

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

	"coopcloud.tech/abra/cli/internal"
	"coopcloud.tech/abra/pkg/autocomplete"
	"coopcloud.tech/abra/pkg/client"
	"coopcloud.tech/abra/pkg/config"
	"coopcloud.tech/abra/pkg/recipe"
	stack "coopcloud.tech/abra/pkg/upstream/stack"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	dockerClient "github.com/docker/docker/client"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

var appErrorsCommand = cli.Command{
	Name:      "errors",
	Usage:     "List errors for a deployed app",
	ArgsUsage: "<domain>",
	Description: `
List errors for a deployed app.

This is a best-effort implementation and an attempt to gather a number of tips
& tricks for finding errors together into one convenient command. When an app
is failing to deploy or having issues, it could be a lot of things.

This command currently takes into account:

    Is the service deployed?
    Is the service killed by an OOM error?
    Is the service reporting an error (like in "ps --no-trunc" output)
    Is the service healthcheck failing? what are the healthcheck logs?

Got any more ideas? Please let us know:

    https://git.coopcloud.tech/coop-cloud/organising/issues/new/choose

This command is best accompanied by "abra app logs <domain>" which may reveal
further information which can help you debug the cause of an app failure via
the logs.
`,
	Aliases: []string{"e"},
	Flags: []cli.Flag{
		internal.DebugFlag,
		internal.WatchFlag,
		internal.OfflineFlag,
	},
	Before:       internal.SubCommandBefore,
	BashComplete: autocomplete.AppNameComplete,
	Action: func(c *cli.Context) error {
		app := internal.ValidateApp(c)

		cl, err := client.New(app.Server)
		if err != nil {
			logrus.Fatal(err)
		}

		isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName())
		if err != nil {
			logrus.Fatal(err)
		}

		if !isDeployed {
			logrus.Fatalf("%s is not deployed?", app.Name)
		}

		if !internal.Watch {
			if err := checkErrors(c, cl, app); err != nil {
				logrus.Fatal(err)
			}
			return nil
		}

		for {
			if err := checkErrors(c, cl, app); err != nil {
				logrus.Fatal(err)
			}
			time.Sleep(2 * time.Second)
		}
	},
}

func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error {
	recipe, err := recipe.Get(app.Recipe, internal.Offline)
	if err != nil {
		return err
	}

	for _, service := range recipe.Config.Services {
		filters := filters.NewArgs()
		filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service.Name))

		containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters})
		if err != nil {
			return err
		}

		if len(containers) == 0 {
			logrus.Warnf("%s is not up, something seems wrong", service.Name)
			continue
		}

		container := containers[0]
		containerState, err := cl.ContainerInspect(context.Background(), container.ID)
		if err != nil {
			logrus.Fatal(err)
		}

		if containerState.State.OOMKilled {
			logrus.Warnf("%s has been killed due to an out of memory error", service.Name)
		}

		if containerState.State.Error != "" {
			logrus.Warnf("%s reports this error: %s", service.Name, containerState.State.Error)
		}

		if containerState.State.Health != nil {
			if containerState.State.Health.Status != "healthy" {
				logrus.Warnf("%s healthcheck status is %s", service.Name, containerState.State.Health.Status)
				logrus.Warnf("%s healthcheck has failed %s times", service.Name, strconv.Itoa(containerState.State.Health.FailingStreak))
				for _, log := range containerState.State.Health.Log {
					logrus.Warnf("%s healthcheck logs: %s", service.Name, strings.TrimSpace(log.Output))
				}
			}
		}
	}

	return nil
}

func getServiceName(names []string) string {
	containerName := strings.Join(names, " ")
	trimmed := strings.TrimPrefix(containerName, "/")
	return strings.Split(trimmed, ".")[0]
}