//go:build integration
// +build integration

package docker_test

import (
	"bytes"
	"context"
	"errors"
	"io"
	"strings"
	"testing"
	"time"

	cloudevents "github.com/cloudevents/sdk-go/v2"
	"github.com/cloudevents/sdk-go/v2/protocol/http"
	"github.com/docker/docker/api/types/image"
	dockerClient "github.com/docker/docker/client"

	"knative.dev/func/pkg/docker"
	fn "knative.dev/func/pkg/functions"
	. "knative.dev/func/pkg/testing"
)

const displayEventImg = "gcr.io/knative-releases/knative.dev/eventing/cmd/event_display@sha256:610234e4319b767b187398085971d881956da660a4e0fab65a763e0f81881d82"

func TestRun(t *testing.T) {
	root, cleanup := Mktemp(t)
	defer cleanup()

	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10)
	t.Cleanup(cancel)
	image := displayEventImg
	prePullTestImages(t, image)

	// No need to check for port 8080 since the runner should automatically
	// choose an open port, with 8080 only being the preferred first choice.

	// Initialize a new function (creates all artifacts on disk necessary
	// to perform actions such as running)
	f := fn.Function{Runtime: "go", Root: root, Image: image}

	client := fn.New()
	f, err := client.Init(f)
	if err != nil {
		t.Fatal(err)
	}

	f, err = client.Build(ctx, f)
	if err != nil {
		t.Fatal(err)
	}

	// Run the function using a docker runner
	var out, errOut bytes.Buffer
	runner := docker.NewRunner(true, &out, &errOut)
	j, err := runner.Run(ctx, f, "", fn.DefaultStartTimeout)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = j.Stop() })
	time.Sleep(time.Second * 5)

	var (
		id  = "runner-test-id"
		src = "runner-test-src"
		typ = "runner-test-type"
	)

	event := cloudevents.NewEvent()
	event.SetID(id)
	event.SetSource(src)
	event.SetType(typ)

	c, err := cloudevents.NewClientHTTP(cloudevents.WithTarget("http://localhost:" + j.Port))
	if err != nil {
		t.Fatal(err)
	}

	var httpErr *http.Result
	res := c.Send(ctx, event)
	if ok := errors.As(res, &httpErr); ok {
		if httpErr.StatusCode < 200 || httpErr.StatusCode > 299 {
			t.Fatal("non 2XX code")
		}
	} else {
		t.Error("expected http.Result type")
	}
	time.Sleep(time.Second * 5)

	t.Log("out: ", out.String())
	t.Log("errOut: ", errOut.String())

	outStr := out.String()

	if !(strings.Contains(outStr, id) && strings.Contains(outStr, src) && strings.Contains(outStr, typ)) {
		t.Error("output doesn't contain invocation info")
	}
}

func prePullTestImages(t *testing.T, img string) {
	t.Helper()
	c, _, err := docker.NewClient(dockerClient.DefaultDockerHost)
	if err != nil {
		t.Fatal(err)
	}
	resp, err := c.ImagePull(context.Background(), img, image.PullOptions{})
	if err != nil {
		t.Fatal(err)
	}
	_, _ = io.Copy(io.Discard, resp)
}
