package csplugin

import (
	"context"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"

	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
)

type PluginSuite struct {
	suite.Suite

	// where the plugin is built - temporary directory for the suite
	buildDir string
	// full path to the built plugin binary
	builtBinary string

	runDir       string // temporary directory for each test
	pluginDir    string // (config_paths.plugin_dir)
	notifDir     string // (config_paths.notification_dir)
	pluginBinary string // full path to the plugin binary (unique for each test)
	pluginConfig string // full path to the notification config (unique for each test)
	outFile      string // full path to the output file (unique for each test)

	pluginBroker *PluginBroker
}

func TestPluginSuite(t *testing.T) {
	suite.Run(t, new(PluginSuite))
}

func (s *PluginSuite) SetupSuite() {
	var err error

	t := s.T()

	s.buildDir, err = os.MkdirTemp("", "cs_plugin_test_build")
	require.NoError(t, err)

	s.builtBinary = filepath.Join(s.buildDir, "notification-dummy")

	if runtime.GOOS == "windows" {
		s.builtBinary += ".exe"
	}

	ctx := t.Context()

	cmd := exec.CommandContext(ctx, "go", "build", "-o", s.builtBinary, "../../cmd/notification-dummy/")
	err = cmd.Run()
	require.NoError(t, err, "while building dummy plugin")
}

func (s *PluginSuite) TearDownSuite() {
	t := s.T()
	err := os.RemoveAll(s.buildDir)
	require.NoError(t, err)
}

func copyFile(src string, dst string) error {
	s, err := os.Open(src)
	if err != nil {
		return err
	}
	defer s.Close()

	d, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer d.Close()

	_, err = io.Copy(d, s)
	if err != nil {
		return err
	}

	err = d.Sync()
	if err != nil {
		return err
	}

	return nil
}

func (s *PluginSuite) SetupTest() {
	s.SetupSubTest()
}

func (s *PluginSuite) TearDownTest() {
	s.TearDownSubTest()
}

func (s *PluginSuite) SetupSubTest() {
	var err error

	t := s.T()

	s.runDir, err = os.MkdirTemp("", "cs_plugin_test")
	require.NoError(t, err)

	s.pluginDir = filepath.Join(s.runDir, "bin")
	err = os.MkdirAll(filepath.Join(s.runDir, "bin"), 0o755)
	require.NoError(t, err, "while creating bin dir")

	s.notifDir = filepath.Join(s.runDir, "config")
	err = os.MkdirAll(s.notifDir, 0o755)
	require.NoError(t, err, "while creating config dir")

	s.pluginBinary = filepath.Join(s.pluginDir, "notification-dummy")

	if runtime.GOOS == "windows" {
		s.pluginBinary += ".exe"
	}

	err = copyFile(s.builtBinary, s.pluginBinary)
	require.NoError(t, err, "while copying built binary")

	err = os.Chmod(s.pluginBinary, 0o744)
	require.NoError(t, err, "chmod 0744 %s", s.pluginBinary)

	s.pluginConfig = filepath.Join(s.notifDir, "dummy.yaml")
	err = copyFile("testdata/dummy.yaml", s.pluginConfig)
	require.NoError(t, err, "while copying plugin config")

	s.outFile = filepath.Join(t.TempDir(), "out")
	t.Setenv("OUTFILE", s.outFile)
}

func (s *PluginSuite) TearDownSubTest() {
	t := s.T()

	if s.pluginBroker != nil {
		s.pluginBroker.Kill()
		s.pluginBroker = nil
	}

	err := os.RemoveAll(s.runDir)
	if runtime.GOOS != "windows" {
		require.NoError(t, err)
	}
}

func (s *PluginSuite) InitBroker(ctx context.Context, procCfg *csconfig.PluginCfg) (*PluginBroker, error) {
	pb := PluginBroker{}

	if procCfg == nil {
		procCfg = &csconfig.PluginCfg{}
	}

	profiles := csconfig.NewDefaultConfig().API.Server.Profiles
	profiles = append(profiles, &csconfig.ProfileCfg{
		Notifications: []string{"dummy_default"},
	})

	err := pb.Init(ctx, procCfg, profiles, &csconfig.ConfigurationPaths{
		PluginDir:       s.pluginDir,
		NotificationDir: s.notifDir,
	})

	s.pluginBroker = &pb

	return s.pluginBroker, err
}
