package setup

import (
	"context"
	"fmt"
	"os/exec"
	"path/filepath"
	"time"

	"gopkg.in/yaml.v3"

	"github.com/alibaba/ilogtail/pkg/logger"
	"github.com/alibaba/ilogtail/pkg/protocol"
	"github.com/alibaba/ilogtail/test/config"
	"github.com/alibaba/ilogtail/test/engine/setup/controller"
	"github.com/alibaba/ilogtail/test/engine/setup/dockercompose"
)

const dependencyHome = "test_cases"

type DockerComposeEnv struct {
	BootController *controller.BootController
	BootType       dockercompose.BootType
}

func SetDockerComposeBootType(t dockercompose.BootType) error {
	if dockerComposeEnv, ok := Env.(*DockerComposeEnv); ok {
		if t != dockercompose.DockerComposeBootTypeE2E && t != dockercompose.DockerComposeBootTypeBenchmark {
			logger.Error(context.Background(), "BOOT_LOAD_ALARM", "err", "invalid docker compose boot type, not e2e or benchmark")
			return fmt.Errorf("invalid docker compose boot type, not e2e or benchmark")
		}
		dockerComposeEnv.BootType = t
		return nil
	}
	return fmt.Errorf("env is not docker-compose")
}

func StartDockerComposeEnv(ctx context.Context, dependencyName string) (context.Context, error) {
	if dockerComposeEnv, ok := Env.(*DockerComposeEnv); ok {
		path := dependencyHome + "/" + dependencyName
		err := config.Load(path)
		if err != nil {
			logger.Error(ctx, "LOAD_CONFIG_ALARM", "err", err)
			return ctx, err
		}
		dockerComposeEnv.BootController = new(controller.BootController)
		if err = dockerComposeEnv.BootController.Init(dockerComposeEnv.BootType); err != nil {
			logger.Error(ctx, "BOOT_INIT_ALARM", "err", err)
			return ctx, err
		}

		startTime := time.Now().Unix()
		if err = dockerComposeEnv.BootController.Start(ctx); err != nil {
			logger.Error(ctx, "BOOT_START_ALARM", "err", err)
			return ctx, err
		}
		return context.WithValue(ctx, config.StartTimeContextKey, int32(startTime)), nil
	}
	return ctx, fmt.Errorf("env is not docker-compose")
}

func SetDockerComposeDependOn(ctx context.Context, dependOnContainers string) (context.Context, error) {
	if _, ok := Env.(*DockerComposeEnv); ok {
		containers := make([]string, 0)
		err := yaml.Unmarshal([]byte(dependOnContainers), &containers)
		if err != nil {
			logger.Error(ctx, "LOAD_CONFIG_ALARM", "err", err)
			return ctx, err
		}
		ctx = context.WithValue(ctx, config.DependOnContainerKey, containers)
	} else {
		return ctx, fmt.Errorf("env is not docker-compose")
	}
	return ctx, nil
}

func MountVolume(ctx context.Context, source, target string) (context.Context, error) {
	if _, ok := Env.(*DockerComposeEnv); ok {
		var existVolume []string
		var ok bool
		if existVolume, ok = ctx.Value(config.MountVolumeKey).([]string); ok {
			existVolume = append(existVolume, source+":"+target)
		} else {
			existVolume = []string{source + ":" + target}
		}
		ctx = context.WithValue(ctx, config.MountVolumeKey, existVolume)
	} else {
		return ctx, fmt.Errorf("env is not docker-compose")
	}
	return ctx, nil
}

func ExposePort(ctx context.Context, source, target string) (context.Context, error) {
	if _, ok := Env.(*DockerComposeEnv); ok {
		var existPort []string
		var ok bool
		if existPort, ok = ctx.Value(config.ExposePortKey).([]string); ok {
			existPort = append(existPort, source+":"+target)
		} else {
			existPort = []string{source + ":" + target}
		}
		ctx = context.WithValue(ctx, config.ExposePortKey, existPort)
	} else {
		return ctx, fmt.Errorf("env is not docker-compose")
	}
	return ctx, nil
}

func NewDockerComposeEnv() *DockerComposeEnv {
	env := &DockerComposeEnv{}
	root, _ := filepath.Abs(".")
	config.ConfigDir = root + "/config"
	config.OnetimeConfigDir = root + "/onetime_pipeline_config"
	env.BootType = dockercompose.DockerComposeBootTypeE2E
	return env
}

func (d *DockerComposeEnv) GetType() string {
	return "docker-compose"
}

func (d *DockerComposeEnv) GetData() (*protocol.LogGroup, error) {
	return nil, fmt.Errorf("not implemented")
}

func (d *DockerComposeEnv) Clean() error {
	d.BootController.Clean()
	return nil
}

func (d *DockerComposeEnv) ExecOnLoongCollector(command string) (string, error) {
	// exec on host of docker compose
	logger.Info(context.Background(), "ExecOnLoongCollector", command)
	cmd := exec.Command("sh", "-c", command)
	output, err := cmd.CombinedOutput()
	logger.Info(context.Background(), "ExecOnLoongCollector", command, "output", string(output))
	return string(output), err
}

func (d *DockerComposeEnv) ExecOnSource(ctx context.Context, command string) (string, error) {
	// exec on host of docker compose
	logger.Info(ctx, "ExecOnSource", command)
	cmd := exec.Command("sh", "-c", command)
	output, err := cmd.CombinedOutput()
	logger.Info(ctx, "ExecOnSource", command, "output", string(output))
	return string(output), err
}
