package app

import (
	"encoding/json"
	"fmt"
	"sort"
	"strconv"
	"strings"

	"coopcloud.tech/abra/cli/internal"
	appPkg "coopcloud.tech/abra/pkg/app"
	"coopcloud.tech/abra/pkg/formatter"
	"coopcloud.tech/abra/pkg/log"
	"coopcloud.tech/tagcmp"
	"github.com/urfave/cli"
)

var (
	status     bool
	statusFlag = &cli.BoolFlag{
		Name:        "status, S",
		Usage:       "Show app deployment status",
		Destination: &status,
	}
)

var (
	recipeFilter string
	recipeFlag   = &cli.StringFlag{
		Name:        "recipe, r",
		Value:       "",
		Usage:       "Show apps of a specific recipe",
		Destination: &recipeFilter,
	}
)

var (
	listAppServer     string
	listAppServerFlag = &cli.StringFlag{
		Name:        "server, s",
		Value:       "",
		Usage:       "Show apps of a specific server",
		Destination: &listAppServer,
	}
)

type appStatus struct {
	Server       string `json:"server"`
	Recipe       string `json:"recipe"`
	AppName      string `json:"appName"`
	Domain       string `json:"domain"`
	Status       string `json:"status"`
	Chaos        string `json:"chaos"`
	ChaosVersion string `json:"chaosVersion"`
	AutoUpdate   string `json:"autoUpdate"`
	Version      string `json:"version"`
	Upgrade      string `json:"upgrade"`
}

type serverStatus struct {
	Apps             []appStatus `json:"apps"`
	AppCount         int         `json:"appCount"`
	VersionCount     int         `json:"versionCount"`
	UnversionedCount int         `json:"unversionedCount"`
	LatestCount      int         `json:"latestCount"`
	UpgradeCount     int         `json:"upgradeCount"`
}

var appListCommand = cli.Command{
	Name:    "list",
	Aliases: []string{"ls"},
	Usage:   "List all managed apps",
	Description: `
Read the local file system listing of apps and servers (e.g. ~/.abra/) to
generate a report of all your apps.

By passing the "--status/-S" flag, you can query all your servers for the
actual live deployment status. Depending on how many servers you manage, this
can take some time.`,
	Flags: []cli.Flag{
		internal.DebugFlag,
		internal.MachineReadableFlag,
		statusFlag,
		listAppServerFlag,
		recipeFlag,
		internal.OfflineFlag,
	},
	Before: internal.SubCommandBefore,
	Action: func(c *cli.Context) error {
		appFiles, err := appPkg.LoadAppFiles(listAppServer)
		if err != nil {
			log.Fatal(err)
		}

		apps, err := appPkg.GetApps(appFiles, recipeFilter)
		if err != nil {
			log.Fatal(err)
		}

		sort.Sort(appPkg.ByServerAndRecipe(apps))

		statuses := make(map[string]map[string]string)
		if status {
			alreadySeen := make(map[string]bool)
			for _, app := range apps {
				if _, ok := alreadySeen[app.Server]; !ok {
					alreadySeen[app.Server] = true
				}
			}

			statuses, err = appPkg.GetAppStatuses(apps, internal.MachineReadable)
			if err != nil {
				log.Fatal(err)
			}
		}

		var totalServersCount int
		var totalAppsCount int
		allStats := make(map[string]serverStatus)
		for _, app := range apps {
			var stats serverStatus
			var ok bool
			if stats, ok = allStats[app.Server]; !ok {
				stats = serverStatus{}
				if recipeFilter == "" {
					// count server, no filtering
					totalServersCount++
				}
			}

			if app.Recipe.Name == recipeFilter || recipeFilter == "" {
				if recipeFilter != "" {
					// only count server if matches filter
					totalServersCount++
				}

				appStats := appStatus{}
				stats.AppCount++
				totalAppsCount++

				if status {
					status := "unknown"
					version := "unknown"
					chaos := "unknown"
					chaosVersion := "unknown"
					autoUpdate := "unknown"
					if statusMeta, ok := statuses[app.StackName()]; ok {
						if currentVersion, exists := statusMeta["version"]; exists {
							if currentVersion != "" {
								version = currentVersion
							}
						}
						if chaosDeploy, exists := statusMeta["chaos"]; exists {
							chaos = chaosDeploy
						}
						if chaosDeployVersion, exists := statusMeta["chaosVersion"]; exists {
							chaosVersion = chaosDeployVersion
						}
						if autoUpdateState, exists := statusMeta["autoUpdate"]; exists {
							autoUpdate = autoUpdateState
						}
						if statusMeta["status"] != "" {
							status = statusMeta["status"]
						}
						stats.VersionCount++
					} else {
						stats.UnversionedCount++
					}

					appStats.Status = status
					appStats.Chaos = chaos
					appStats.ChaosVersion = chaosVersion
					appStats.Version = version
					appStats.AutoUpdate = autoUpdate

					var newUpdates []string
					if version != "unknown" {
						updates, err := app.Recipe.Tags()
						if err != nil {
							log.Fatal(err)
						}

						parsedVersion, err := tagcmp.Parse(version)
						if err != nil {
							log.Fatal(err)
						}

						for _, update := range updates {
							parsedUpdate, err := tagcmp.Parse(update)
							if err != nil {
								log.Fatal(err)
							}

							if update != version && parsedUpdate.IsGreaterThan(parsedVersion) {
								newUpdates = append(newUpdates, update)
							}
						}
					}

					if len(newUpdates) == 0 {
						if version == "unknown" {
							appStats.Upgrade = "unknown"
						} else {
							appStats.Upgrade = "latest"
							stats.LatestCount++
						}
					} else {
						newUpdates = internal.ReverseStringList(newUpdates)
						appStats.Upgrade = strings.Join(newUpdates, "\n")
						stats.UpgradeCount++
					}
				}

				appStats.Server = app.Server
				appStats.Recipe = app.Recipe.Name
				appStats.AppName = app.Name
				appStats.Domain = app.Domain

				stats.Apps = append(stats.Apps, appStats)
			}
			allStats[app.Server] = stats
		}

		if internal.MachineReadable {
			jsonstring, err := json.Marshal(allStats)
			if err != nil {
				log.Fatal(err)
			} else {
				fmt.Println(string(jsonstring))
			}
			return nil
		}

		alreadySeen := make(map[string]bool)
		for _, app := range apps {
			if _, ok := alreadySeen[app.Server]; ok {
				continue
			}

			serverStat := allStats[app.Server]

			headers := []string{"RECIPE", "DOMAIN"}
			if status {
				headers = append(headers, []string{
					"STATUS",
					"CHAOS",
					"VERSION",
					"UPGRADE",
					"AUTOUPDATE"}...,
				)
			}

			table, err := formatter.CreateTable()
			if err != nil {
				log.Fatal(err)
			}

			table.Headers(headers...)

			var rows [][]string
			for _, appStat := range serverStat.Apps {
				row := []string{appStat.Recipe, appStat.Domain}
				if status {
					chaosStatus := appStat.Chaos
					if chaosStatus != "unknown" {
						chaosEnabled, err := strconv.ParseBool(chaosStatus)
						if err != nil {
							log.Fatal(err)
						}
						if chaosEnabled && appStat.ChaosVersion != "unknown" {
							chaosStatus = appStat.ChaosVersion
						}
					}

					row = append(row, []string{
						appStat.Status,
						chaosStatus,
						appStat.Version,
						appStat.Upgrade,
						appStat.AutoUpdate}...,
					)
				}

				rows = append(rows, row)
			}

			table.Rows(rows...)

			if len(rows) > 0 {
				fmt.Println(table)

				if status {
					fmt.Println(fmt.Sprintf(
						"SERVER: %s | TOTAL APPS: %v | VERSIONED: %v | UNVERSIONED: %v | LATEST : %v | UPGRADE: %v",
						app.Server,
						serverStat.AppCount,
						serverStat.VersionCount,
						serverStat.UnversionedCount,
						serverStat.LatestCount,
						serverStat.UpgradeCount,
					))
				} else {
					log.Infof("SERVER: %s TOTAL APPS: %v", app.Server, serverStat.AppCount)
				}

				if len(allStats) > 1 && len(rows) > 0 {
					fmt.Println() // newline separator for multiple servers
				}
			}

			alreadySeen[app.Server] = true
		}

		if len(allStats) > 1 {
			totalServers := formatter.BoldStyle.Render("TOTAL SERVERS")
			totalApps := formatter.BoldStyle.Render("TOTAL APPS")
			log.Infof("%s: %v | %s: %v ", totalServers, totalServersCount, totalApps, totalAppsCount)
		}

		return nil
	},
}
