package commands

import (
	"bufio"
	"bytes"
	"database/sql"
	"fmt"
	"os"
	"os/exec"
	"sort"
	"time"

	"github.com/briandowns/spinner"
	"github.com/cenkalti/backoff"
	_ "github.com/lib/pq"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

	"github.com/contiamo/dev/cli/config"
)

func stopLocaldev(cmd *cobra.Command, clean bool) (err error) {
	compose := exec.CommandContext(
		cmd.Context(),
		"docker-compose",
		"down",
		"--remove-orphans",
	)
	compose.Dir = config.LocaldevRoot()
	compose.Stderr = cmd.OutOrStderr()
	compose.Stdout = cmd.OutOrStdout()
	compose.Env = os.Environ()

	if clean {
		compose.Args = append(compose.Args, "--volumes")
	}

	return compose.Run()
}

func startLocaldev(cmd *cobra.Command, withIngester, withPantheonRedir bool, env []string) (err error) {
	err = startDB(cmd)
	if err != nil {
		return err
	}
	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
	s.Suffix = " start and load metadb"
	s.Start()
	err = waitForDB(cmd)
	if err != nil {
		return err
	}
	s.Stop()
	fmt.Fprintln(cmd.OutOrStdout())

	composeFiles := []string{"-f", "docker-compose.yml"}
	if withIngester {
		composeFiles = append(composeFiles, "-f", "docker-compose-extra.yml")
	}

	composeArgs := append(composeFiles, "up", "-d", "-t", Timeout, "--remove-orphans")

	compose := exec.CommandContext(
		cmd.Context(),
		"docker-compose",
		composeArgs...,
	)
	compose.Dir = config.LocaldevRoot()
	compose.Stderr = cmd.OutOrStderr()
	compose.Stdout = cmd.OutOrStdout()
	compose.Env = os.Environ()

	if withPantheonRedir {
		compose.Env = append(compose.Env, "PANTHEON_IMAGE="+config.PantheonRedirImage())
	}

	if len(env) > 0 {
		compose.Env = append(compose.Env, env...)
	}

	return compose.Run()
}

func stopServices(cmd *cobra.Command, withIngester, withPantheonRedir bool, env []string, args []string) (err error) {
	if withPantheonRedir {
		env = append(env, "PANTHEON_IMAGE="+config.PantheonRedirImage())
	}

	composeFiles := []string{"-f", "docker-compose.yml"}
	if withIngester {
		composeFiles = append(composeFiles, "-f", "docker-compose-extra.yml")
	}

	stopArgs := append(composeFiles, "rm", "--force", "--stop")
	stopArgs = append(stopArgs, args...)

	stop := exec.CommandContext(
		cmd.Context(),
		"docker-compose",
		stopArgs...,
	)
	stop.Dir = config.LocaldevRoot()
	stop.Stderr = cmd.OutOrStderr()
	stop.Stdout = cmd.OutOrStdout()
	stop.Env = env

	return stop.Run()
}

func waitForDB(cmd *cobra.Command) error {
	plan := backoff.NewConstantBackOff(time.Second)
	backoff.WithContext(plan, cmd.Context())
	return backoff.Retry(func() error {
		db, err := sql.Open("postgres", "sslmode=disable host=localhost port=5433 dbname=simpleidp user=user password=localdev")
		if err != nil {
			logrus.WithError(err).Debug("can not open db connection")
			return err
		}
		err = db.Ping()
		if err != nil {
			return err
		}

		row := db.QueryRowContext(cmd.Context(), "select slug from tenants where slug = 'lemonade-shop'")
		if row.Err() == sql.ErrNoRows {
			fmt.Fprintln(cmd.OutOrStdout(), "Lemon Shop tenant not installed, use \"localdev stop --clean && localdev restore\" to load the dev snapshot")
			return nil
		}

		return row.Err()
	}, plan)
}

func startDB(cmd *cobra.Command) (err error) {
	compose := exec.CommandContext(
		cmd.Context(),
		"docker-compose",
		"up", "-d", "-t", Timeout, "metadb",
	)
	compose.Dir = config.LocaldevRoot()
	compose.Stderr = cmd.OutOrStderr()
	compose.Stdout = cmd.OutOrStdout()
	compose.Env = os.Environ()

	return compose.Run()
}

// listServices is equivalent to `docker-compose config --services`
func listServices(cmd *cobra.Command, showInternal bool) (services []string, err error) {
	var buf bytes.Buffer

	compose := exec.CommandContext(
		cmd.Context(),
		"docker-compose",
		"-f", "docker-compose.yml",
		"-f", "docker-compose-extra.yml",
		"config", "--services",
	)
	compose.Dir = config.LocaldevRoot()
	compose.Stderr = cmd.OutOrStderr()
	compose.Stdout = &buf
	compose.Env = os.Environ()

	err = compose.Run()
	if err != nil {
		return nil, err
	}

	sc := bufio.NewScanner(&buf)
	for sc.Scan() {
		name := sc.Text()

		isInternal := internalServices[name]
		if !showInternal && isInternal {
			continue
		}

		services = append(services, name)
	}

	sort.Strings(services)
	return services, nil
}

var internalServices = map[string]bool{
	"tracing":     true,
	"smtpmock":    true,
	"redis":       true,
	"nginx":       true,
	"blobstorage": true,
}
