package internal

import (
	"bufio"
	"context"
	"fmt"
	"io/ioutil"
	"os/exec"
	"strings"

	appPkg "coopcloud.tech/abra/pkg/app"
	containerPkg "coopcloud.tech/abra/pkg/container"
	"coopcloud.tech/abra/pkg/formatter"
	"coopcloud.tech/abra/pkg/log"
	"coopcloud.tech/abra/pkg/upstream/container"
	"github.com/docker/cli/cli/command"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	dockerClient "github.com/docker/docker/client"
	"github.com/docker/docker/pkg/archive"
)

// RunCmdRemote executes an abra.sh command in the target service
func RunCmdRemote(
	cl *dockerClient.Client,
	app appPkg.App,
	requestTTY bool,
	abraSh, serviceName, cmdName, cmdArgs, remoteUser string) error {
	filters := filters.NewArgs()
	filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), serviceName))

	targetContainer, err := containerPkg.GetContainer(context.Background(), cl, filters, false)
	if err != nil {
		return err
	}

	log.Debugf("retrieved %s as target container on %s", formatter.ShortenID(targetContainer.ID), app.Server)

	toTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip}
	content, err := archive.TarWithOptions(abraSh, toTarOpts)
	if err != nil {
		return err
	}

	copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false}
	if err := cl.CopyToContainer(context.Background(), targetContainer.ID, "/tmp", content, copyOpts); err != nil {
		return err
	}

	// FIXME: avoid instantiating a new CLI
	dcli, err := command.NewDockerCli()
	if err != nil {
		return err
	}

	shell := "/bin/bash"
	findShell := []string{"test", "-e", shell}
	execCreateOpts := types.ExecConfig{
		AttachStderr: true,
		AttachStdin:  true,
		AttachStdout: true,
		Cmd:          findShell,
		Detach:       false,
		Tty:          false,
	}

	if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
		log.Infof("%s does not exist for %s, use /bin/sh as fallback", shell, app.Name)
		shell = "/bin/sh"
	}

	var cmd []string
	if cmdArgs != "" {
		cmd = []string{shell, "-c", fmt.Sprintf("TARGET=%s; APP_NAME=%s; STACK_NAME=%s; . /tmp/abra.sh; %s %s", serviceName, app.Name, app.StackName(), cmdName, cmdArgs)}
	} else {
		cmd = []string{shell, "-c", fmt.Sprintf("TARGET=%s; APP_NAME=%s; STACK_NAME=%s; . /tmp/abra.sh; %s", serviceName, app.Name, app.StackName(), cmdName)}
	}

	log.Debugf("running command: %s", strings.Join(cmd, " "))

	if remoteUser != "" {
		log.Debugf("running command with user %s", remoteUser)
		execCreateOpts.User = remoteUser
	}

	execCreateOpts.Cmd = cmd
	execCreateOpts.Tty = requestTTY
	if !requestTTY {
		log.Debugf("not requesting a remote TTY")
	}

	if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
		return err
	}

	return nil
}

func EnsureCommand(abraSh, recipeName, execCmd string) error {
	bytes, err := ioutil.ReadFile(abraSh)
	if err != nil {
		return err
	}

	if !strings.Contains(string(bytes), execCmd) {
		return fmt.Errorf("%s doesn't have a %s function", recipeName, execCmd)
	}

	return nil
}

// RunCmd runs a shell command and streams stdout/stderr in real-time.
func RunCmd(cmd *exec.Cmd) error {
	r, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	cmd.Stderr = cmd.Stdout
	done := make(chan struct{})
	scanner := bufio.NewScanner(r)

	go func() {
		for scanner.Scan() {
			line := scanner.Text()
			fmt.Println(line)
		}
		done <- struct{}{}
	}()

	if err := cmd.Start(); err != nil {
		return err
	}

	<-done

	if err := cmd.Wait(); err != nil {
		return err
	}

	return nil
}
