// Copyright 2023 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0

//go:build !bench && !race

package jobqueue

import (
	"context"
	"fmt"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
	"github.com/stretchr/testify/assert"
	"net"
	"os"
	"testing"
	"time"
)

const DOCKER_TEST_HOST_IP = "127.0.0.1"

func startTestSMTPDockerImageAndContainer(t *testing.T, port string, ctx context.Context) error {
	t.Helper()

	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return err
	}

	imageName := "axllent/mailpit"

	reader, err := cli.ImagePull(ctx, imageName, types.ImagePullOptions{})
	if err != nil {
		return err
	}

	// if debug image pull, comment out the following lines
	//_, _ = io.Copy(os.Stdout, reader)
	_ = reader

	hostConfig := &container.HostConfig{
		PortBindings: nat.PortMap{
			"1025/tcp": []nat.PortBinding{
				{
					HostIP:   DOCKER_TEST_HOST_IP,
					HostPort: port,
				},
			},
			// if you want to test the web interface, uncomment the following lines
			//"8025/tcp": []nat.PortBinding{
			//	{
			//		HostIP:   DOCKER_TEST_HOST_IP,
			//		HostPort: "8025",
			//	},
			//},
		},
	}

	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image: imageName,
	}, hostConfig, nil, nil, "")

	if err != nil {
		return err
	}

	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		return err
	}

	go func() {
		<-ctx.Done()

		timeout := 0
		stopOptions := container.StopOptions{
			Timeout: &timeout,
			Signal:  "SIGKILL",
		}
		newCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
		defer cancel()
		if err := cli.ContainerStop(newCtx, resp.ID, stopOptions); err != nil {
			t.Errorf("ContainerStop returned error: %v", err)
		}
		if err := cli.ContainerRemove(newCtx, resp.ID, types.ContainerRemoveOptions{
			Force: true,
		}); err != nil {
			t.Errorf("ContainerRemove returned error: %v", err)
		}

	}()

	statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
	select {
	case err := <-errCh:
		if err != nil {
			// empty error means container exited normally (see container_wait.go)
			if err.Error() == "" {
				return nil
			}

			return err
		}
	case <-statusCh:

	}

	return nil
}

func TestMailRunner(t *testing.T) {
	if os.Getenv("CI_SERVER") != "" {
		t.Skip("Skipping test because CI_SERVER is set")
		// TODO: run this test in CI
	}

	ctb := context.Background()
	ctx, cancel := context.WithCancel(ctb)
	t.Cleanup(func() {
		cancel()
		time.Sleep(1 * time.Second)
	})

	listener, err := net.Listen("tcp", DOCKER_TEST_HOST_IP+":0")
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
		return
	}
	portAsInt := listener.Addr().(*net.TCPAddr).Port
	portAsString := fmt.Sprintf("%d", portAsInt)
	_ = listener.Close()

	done := make(chan bool)
	go func() {
		err = startTestSMTPDockerImageAndContainer(t, portAsString, ctx)
		if err != nil {
			t.Errorf("Unexpected error: %v", err)
			cancel()
		}
		done <- true
	}()

	waitCtx, waitCancel := context.WithTimeout(ctx, 60*time.Second)
	defer waitCancel()
	for {
		conn, err := net.DialTimeout("tcp", net.JoinHostPort(DOCKER_TEST_HOST_IP, portAsString), 1*time.Second)
		if err == nil {
			err = conn.Close()
			assert.Nil(t, err)
			break
		}
		select {
		case <-waitCtx.Done():
			t.Error("Timeout waiting for container service")
			cancel()
			return
		default:
			time.Sleep(1 * time.Second)
		}
	}

	time.Sleep(1 * time.Second)

	mailRunnable := &MailRunnable{
		To:       "to@example.com",
		From:     "from@example.com",
		Subject:  "this is a test",
		Body:     "this is the body",
		Server:   DOCKER_TEST_HOST_IP,
		Port:     portAsString,
		Username: "",
		Password: "",
		Headers: map[string]string{
			"X-Test": "test",
		},
	}

	xtx := context.Background()
	result, err := mailRunnable.Run(xtx)

	// Assertions
	assert.NoError(t, err)
	assert.Equal(t, ResultStatusSuccess, result.Status)
	assert.IsType(t, MailResult{}, result.Data)

	// check result.Data contains 4 files
	mailResult := result.Data.Sent
	assert.Equal(t, true, mailResult)

	cancel()

	select {
	case <-done:
		time.Sleep(1 * time.Second)
	case <-time.After(1 * time.Minute):
		t.Error("test hangs, timeout reached")
	}

}