package internal

import (
	"errors"
	"fmt"
	"os"
	"sort"
	"strings"

	appPkg "coopcloud.tech/abra/pkg/app"
	"coopcloud.tech/abra/pkg/config"
	"coopcloud.tech/abra/pkg/formatter"
	"coopcloud.tech/abra/pkg/i18n"
	"coopcloud.tech/abra/pkg/log"
	"coopcloud.tech/tagcmp"
	"github.com/AlecAivazis/survey/v2"
	"github.com/charmbracelet/lipgloss"
	dockerClient "github.com/docker/docker/client"
)

var borderStyle = lipgloss.NewStyle().
	BorderStyle(lipgloss.ThickBorder()).
	Padding(0, 1, 0, 1).
	MaxWidth(79).
	BorderForeground(lipgloss.Color("63"))

var headerStyle = lipgloss.NewStyle().
	Underline(true).
	Bold(true).
	PaddingBottom(1)

var leftStyle = lipgloss.NewStyle().
	Bold(true)

var rightStyle = lipgloss.NewStyle()

// horizontal is a JoinHorizontal helper function.
func horizontal(left, mid, right string) string {
	return lipgloss.JoinHorizontal(lipgloss.Left, left, mid, right)
}

func formatComposeFiles(composeFiles string) string {
	return strings.ReplaceAll(composeFiles, ":", "\n")
}

// DeployOverview shows a deployment overview
func DeployOverview(
	app appPkg.App,
	deployedVersion string,
	toDeployVersion string,
	releaseNotes string,
	warnMessages []string,
	secrets []string,
	configs []string,
	images []string,
) error {
	deployConfig := "compose.yml"
	if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
		deployConfig = formatComposeFiles(composeFiles)
	}

	server := app.Server
	if app.Server == "default" {
		server = "local"
	}

	domain := fmt.Sprintf("https://%s", app.Domain)
	if domain == "" {
		domain = config.MISSING_DEFAULT
	}

	envVersion := app.Recipe.EnvVersionRaw
	if envVersion == "" {
		envVersion = config.MISSING_DEFAULT
	}

	rows := [][]string{
		{i18n.G("DOMAIN"), domain},
		{i18n.G("RECIPE"), app.Recipe.Name},
		{i18n.G("SERVER"), server},
		{i18n.G("CONFIG"), deployConfig},
		{"", ""},
		{i18n.G("CURRENT DEPLOYMENT"), formatter.BoldDirtyDefault(deployedVersion)},
		{i18n.G("ENV VERSION"), formatter.BoldDirtyDefault(envVersion)},
		{i18n.G("NEW DEPLOYMENT"), formatter.BoldDirtyDefault(toDeployVersion)},
	}

	if len(images) > 0 {
		imageRows := [][]string{
			{"", ""},
			{i18n.G("IMAGES"), strings.Join(images, "\n")},
		}
		rows = append(rows, imageRows...)
	}

	if len(secrets) > 0 {
		secretsRows := [][]string{
			{"", ""},
			{i18n.G("SECRETS"), strings.Join(secrets, "\n")},
		}
		rows = append(rows, secretsRows...)
	}

	if len(configs) > 0 {
		configsRows := [][]string{
			{"", ""},
			{i18n.G("CONFIGS"), strings.Join(configs, "\n")},
		}
		rows = append(rows, configsRows...)
	}

	deployType := getDeployType(deployedVersion, toDeployVersion)
	overview := formatter.CreateOverview(i18n.G("%s OVERVIEW", deployType), rows)

	fmt.Println(overview)

	if releaseNotes != "" {
		fmt.Print(releaseNotes)
	}

	for _, msg := range warnMessages {
		log.Warn(msg)
	}

	if NoInput {
		return nil
	}

	response := false
	prompt := &survey.Confirm{Message: i18n.G("proceed?")}
	if err := survey.AskOne(prompt, &response); err != nil {
		return err
	}

	if !response {
		log.Fatal(i18n.G("deployment cancelled"))
	}

	return nil
}

func getDeployType(currentVersion, newVersion string) string {
	if newVersion == config.MISSING_DEFAULT {
		return i18n.G("UNDEPLOY")
	}

	if strings.Contains(newVersion, "+U") {
		return i18n.G("CHAOS DEPLOY")
	}

	if strings.Contains(currentVersion, "+U") {
		return i18n.G("UNCHAOS DEPLOY")
	}

	if currentVersion == newVersion {
		return ("REDEPLOY")
	}

	if currentVersion == config.MISSING_DEFAULT {
		return i18n.G("NEW DEPLOY")
	}

	currentParsed, err := tagcmp.Parse(currentVersion)
	if err != nil {
		return i18n.G("DEPLOY")
	}

	newParsed, err := tagcmp.Parse(newVersion)
	if err != nil {
		return i18n.G("DEPLOY")
	}

	if currentParsed.IsLessThan(newParsed) {
		return i18n.G("UPGRADE")
	}

	return i18n.G("DOWNGRADE")
}

// MoveOverview shows a overview before moving an app to a different server
func MoveOverview(
	app appPkg.App,
	newServer string,
	secrets []string,
	volumes []string,
) {
	server := app.Server
	if app.Server == "default" {
		server = "local"
	}

	domain := app.Domain
	if domain == "" {
		domain = config.MISSING_DEFAULT
	}

	secretsOverview := strings.Join(secrets, "\n")
	if len(secrets) == 0 {
		secretsOverview = config.MISSING_DEFAULT
	}

	volumesOverview := strings.Join(volumes, "\n")
	if len(volumes) == 0 {
		volumesOverview = config.MISSING_DEFAULT
	}

	rows := [][]string{
		{i18n.G("DOMAIN"), domain},
		{i18n.G("RECIPE"), app.Recipe.Name},
		{i18n.G("OLD SERVER"), server},
		{i18n.G("NEW SERVER"), newServer},
		{i18n.G("SECRETS"), secretsOverview},
		{i18n.G("VOLUMES"), volumesOverview},
	}

	overview := formatter.CreateOverview(i18n.G("MOVE OVERVIEW"), rows)

	fmt.Println(overview)
}

func PromptProcced() error {
	if NoInput {
		return nil
	}

	if Dry {
		return errors.New(i18n.G("dry run"))
	}

	response := false
	prompt := &survey.Confirm{Message: i18n.G("proceed?")}
	if err := survey.AskOne(prompt, &response); err != nil {
		return err
	}

	if !response {
		return errors.New(i18n.G("cancelled"))
	}

	return nil
}

// PostCmds parses a string of commands and executes them inside of the respective services
// the commands string must have the following format:
// "<service> <command> <arguments>|<service> <command> <arguments>|... "
func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
	if _, err := os.Stat(app.Recipe.AbraShPath); err != nil {
		if os.IsNotExist(err) {
			return errors.New(i18n.G("%s does not exist for %s?", app.Recipe.AbraShPath, app.Name))
		}
		return err
	}

	for _, command := range strings.Split(commands, "|") {
		commandParts := strings.Split(command, " ")
		if len(commandParts) < 2 {
			return errors.New(i18n.G("not enough arguments: %s", command))
		}
		targetServiceName := commandParts[0]
		cmdName := commandParts[1]
		parsedCmdArgs := ""
		if len(commandParts) > 2 {
			parsedCmdArgs = fmt.Sprintf("%s ", strings.Join(commandParts[2:], " "))
		}
		log.Info(i18n.G("running post-command '%s %s' in container %s", cmdName, parsedCmdArgs, targetServiceName))

		if err := EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
			return err
		}

		serviceNames, err := appPkg.GetAppServiceNames(app.Name)
		if err != nil {
			return err
		}

		matchingServiceName := false
		for _, serviceName := range serviceNames {
			if serviceName == targetServiceName {
				matchingServiceName = true
			}
		}

		if !matchingServiceName {
			return fmt.Errorf("no service %s for %s?", targetServiceName, app.Name)
		}

		log.Debug(i18n.G("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName))

		requestTTY := true
		if err := RunCmdRemote(
			cl,
			app,
			requestTTY,
			app.Recipe.AbraShPath, targetServiceName, cmdName, parsedCmdArgs, ""); err != nil {
			return err
		}
	}
	return nil
}

// SortVersionsDesc sorts versions in descending order.
func SortVersionsDesc(versions []string) []string {
	var tags []tagcmp.Tag

	for _, v := range versions {
		parsed, _ := tagcmp.Parse(v) // skips unsupported tags
		tags = append(tags, parsed)
	}

	sort.Sort(tagcmp.ByTagDesc(tags))

	var desc []string
	for _, t := range tags {
		desc = append(desc, t.String())
	}

	return desc
}
