// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// Copyright twenty-panda <twenty-panda@posteo.com>
// SPDX-License-Identifier: MIT

package hoverfly

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"path"
	"testing"

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

type M interface {
	Run() (code int)
}

type Hoverfly interface {
	Run(m M)

	GetOutput() io.Writer
	SetOutput(io.Writer)

	GetURL() string
}

const (
	modeCapture  = "capture"
	modeSimulate = "simulate"
)

var (
	exit  = os.Exit
	getwd = os.Getwd
)

func Run(t testing.TB, name string) func() {
	t.Helper()

	if !hoverflySingleton.active {
		return func() {}
	}

	httpProxyRestore := func() {}
	if val, ok := os.LookupEnv("http_proxy"); ok {
		httpProxyRestore = func() {
			os.Setenv("http_proxy", val)
		}
	}
	os.Setenv("http_proxy", hoverflySingleton.GetURL())

	if *hoverflySingleton.mode == modeSimulate {
		require.NoError(t, hoverflySingleton.loadSimulation(name))
		require.NoError(t, hoverflySingleton.hoverctl("mode", "simulate", "--matching-strategy", "first"))
		return func() { httpProxyRestore() }
	}

	if *hoverflySingleton.mode == modeCapture {
		require.NoError(t, hoverflySingleton.hoverctl("mode", "capture", "--stateful"))

		return func() {
			httpProxyRestore()
			require.NoError(t, hoverflySingleton.saveSimulation(name))
		}
	}

	panic(fmt.Errorf("unknown mode %s", *hoverflySingleton.mode))
}

func MainTest(m *testing.M) {
	hoverflySingleton.Run(m)
}

type hoverfly struct {
	home      string
	httpProxy *string
	output    io.Writer
	mode      *string
	active    bool
}

var hoverflySingleton = &hoverfly{}

func GetSingleton() Hoverfly {
	return hoverflySingleton
}

func newHoverfly() Hoverfly {
	return &hoverfly{}
}

func (o *hoverfly) hoverctl(args ...string) error {
	hoverctl := "hoverctl"
	cmd := exec.Command(hoverctl, args...)
	cmd.Env = append(
		cmd.Env,
		"HOME="+o.home,
	)
	if output := o.GetOutput(); output != nil {
		cmd.Stdout = output
		cmd.Stderr = output
		fmt.Fprintf(output, "%v: %s %v\n", cmd.Env, hoverctl, args)
	}
	if err := cmd.Run(); err != nil {
		return fmt.Errorf(hoverctl+"start: %w\n", err)
	}
	return nil
}

func (o *hoverfly) getSimulationDir() (string, error) {
	pwd, err := getwd()
	if err != nil {
		return "", err
	}
	p := path.Join(pwd, "hoverfly")
	if err := os.MkdirAll(p, 0o755); err != nil {
		return p, err
	}
	return p, nil
}

func (o *hoverfly) getSimulationPath(name string) (string, error) {
	dir, err := o.getSimulationDir()
	if err != nil {
		return "", err
	}
	return path.Join(dir, name+".json"), nil
}

func (o *hoverfly) loadSimulation(name string) error {
	simulation, err := o.getSimulationPath(name)
	if err != nil {
		return err
	}
	return o.hoverctl("import", simulation)
}

func (o *hoverfly) saveSimulation(name string) error {
	simulation, err := o.getSimulationPath(name)
	if err != nil {
		return err
	}
	return o.hoverctl("export", simulation)
}

func (o *hoverfly) setup() error {
	if val, ok := os.LookupEnv("HOVERFLY"); ok {
		o.active = true
		o.mode = &val
	} else {
		return nil
	}

	home, err := os.MkdirTemp(os.TempDir(), "hoverfly")
	if err != nil {
		return fmt.Errorf("TempDir: %w", err)
	}
	o.home = home
	return o.hoverctl("start")
}

func (o *hoverfly) teardown() error {
	if !o.active {
		return nil
	}
	if err := o.hoverctl("logs"); err != nil {
		return err
	}
	if err := o.hoverctl("stop"); err != nil {
		return err
	}

	if o.home != "" {
		return os.RemoveAll(o.home)
	}
	return nil
}

func (o *hoverfly) SetOutput(output io.Writer) {
	o.output = output
}

func (o *hoverfly) GetOutput() io.Writer {
	return o.output
}

func (o *hoverfly) GetURL() string {
	return "http://localhost:8500"
}

func (o *hoverfly) Run(m M) {
	_ = o.setup()
	exitCode := m.Run()
	_ = o.teardown()
	exit(exitCode)
}
