package cmd

import (
	"context"
	"net/http"
	"os"

	"github.com/blang/semver"
	"github.com/fatih/color"
	"github.com/kong/deck/diff"
	"github.com/kong/deck/dump"
	"github.com/kong/deck/file"
	"github.com/kong/deck/print"
	"github.com/kong/deck/solver"
	"github.com/kong/deck/state"
	"github.com/kong/deck/utils"
	"github.com/kong/go-kong/kong"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

const (
	exitCodeDiffDetection = 2
)

var (
	stopChannel chan struct{}
	dumpConfig  dump.Config
)

// SetStopCh sets the stop channel for long running commands.
// This is useful for cases when a process needs to be cancelled gracefully
// before it can complete to finish. Example: SIGINT
func SetStopCh(stopCh chan struct{}) {
	stopChannel = stopCh
}

// workspaceExists checks if workspace exists in Kong.
func workspaceExists(config utils.KongClientConfig) (bool, error) {
	if config.Workspace == "" {
		// default workspace always exists
		return true, nil
	}

	if config.SkipWorkspaceCrud {
		// if RBAC user, skip check
		return true, nil
	}

	wsClient, err := utils.GetKongClient(config)
	if err != nil {
		return false, err
	}

	_, _, err = wsClient.Routes.List(context.TODO(), nil)
	switch {
	case kong.IsNotFoundErr(err):
		return false, nil
	case err == nil:
		return true, nil
	default:
		return false, errors.Wrap(err, "checking if workspace exists")
	}
}

func syncMain(filenames []string, dry bool, parallelism, delay int, workspace string) error {

	// read target file
	targetContent, err := file.GetContentFromFiles(filenames)
	if err != nil {
		return err
	}

	rootClient, err := utils.GetKongClient(rootConfig)
	if err != nil {
		return err
	}

	var wsConfig utils.KongClientConfig
	// prepare to read the current state from Kong
	if workspace != targetContent.Workspace && workspace != "" {
		print.DeletePrintf("Warning: Workspace '%v' specified via --workspace flag is "+
			"different from workspace '%v' found in state file(s).\n", workspace, targetContent.Workspace)
		wsConfig = rootConfig.ForWorkspace(workspace)
	} else {
		wsConfig = rootConfig.ForWorkspace(targetContent.Workspace)
	}

	// load Kong version after workspace
	kongVersion, err := kongVersion(wsConfig)
	if err != nil {
		return errors.Wrap(err, "reading Kong version")
	}

	workspaceExists, err := workspaceExists(wsConfig)
	if err != nil {
		return err
	}

	wsClient, err := utils.GetKongClient(wsConfig)
	if err != nil {
		return err
	}

	if targetContent.Info != nil {
		dumpConfig.SelectorTags = targetContent.Info.SelectorTags
	}

	// read the current state
	var currentState *state.KongState
	if workspaceExists {
		rawState, err := dump.Get(wsClient, dumpConfig)
		if err != nil {
			return err
		}

		currentState, err = state.Get(rawState)
		if err != nil {
			return err
		}
	} else {

		print.CreatePrintln("creating workspace", wsConfig.Workspace)

		// inject empty state
		currentState, err = state.NewKongState()
		if err != nil {
			return err
		}

		if !dry {
			_, err = rootClient.Workspaces.Create(nil, &kong.Workspace{Name: &wsConfig.Workspace})
			if err != nil {
				return err
			}
		}

	}

	// read the target state
	rawState, err := file.Get(targetContent, file.RenderConfig{
		CurrentState: currentState,
		KongVersion:  kongVersion,
	})
	if err != nil {
		return err
	}
	targetState, err := state.Get(rawState)
	if err != nil {
		return err
	}

	s, _ := diff.NewSyncer(currentState, targetState)
	s.StageDelaySec = delay
	stats, errs := solver.Solve(stopChannel, s, wsClient, parallelism, dry)
	printFn := color.New(color.FgGreen, color.Bold).PrintfFunc()
	printFn("Summary:\n")
	printFn("  Created: %v\n", stats.CreateOps)
	printFn("  Updated: %v\n", stats.UpdateOps)
	printFn("  Deleted: %v\n", stats.DeleteOps)
	if errs != nil {
		return utils.ErrArray{Errors: errs}
	}
	if diffCmdNonZeroExitCode &&
		stats.CreateOps+stats.UpdateOps+stats.DeleteOps != 0 {
		os.Exit(exitCodeDiffDetection)
	}
	return nil
}

func kongVersion(config utils.KongClientConfig) (semver.Version, error) {

	var version string

	workspace := config.Workspace

	// remove workspace to be able to call top-level / endpoint
	config.Workspace = ""
	client, err := utils.GetKongClient(config)
	if err != nil {
		return semver.Version{}, err
	}
	root, err := client.Root(nil)
	if err != nil {
		if workspace == "" {
			return semver.Version{}, err
		}
		// try with workspace path
		req, err := http.NewRequest("GET",
			utils.CleanAddress(config.Address)+"/"+workspace+"/kong",
			nil)
		if err != nil {
			return semver.Version{}, err
		}
		var resp map[string]interface{}
		_, err = client.Do(nil, req, &resp)
		if err != nil {
			return semver.Version{}, err
		}
		version = resp["version"].(string)
	} else {
		version = root["version"].(string)
	}

	v, err := utils.CleanKongVersion(version)
	if err != nil {
		return semver.Version{}, err
	}
	return semver.ParseTolerant(v)
}

func validateNoArgs(cmd *cobra.Command, args []string) error {
	if len(args) > 0 {
		return errors.New("positional arguments are not valid for this command, please use flags instead\n" +
			"Run 'deck --help' for usage.")
	}
	return nil
}
