// Copyright 2024 The Inspektor Gadget authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tests

import (
	"fmt"
	"testing"

	"github.com/stretchr/testify/require"

	gadgettesting "github.com/inspektor-gadget/inspektor-gadget/gadgets/testing"
	igtesting "github.com/inspektor-gadget/inspektor-gadget/pkg/testing"
	"github.com/inspektor-gadget/inspektor-gadget/pkg/testing/containers"
	igrunner "github.com/inspektor-gadget/inspektor-gadget/pkg/testing/ig"
	"github.com/inspektor-gadget/inspektor-gadget/pkg/testing/match"
	"github.com/inspektor-gadget/inspektor-gadget/pkg/testing/utils"
)

type traceTCPretransEvent struct {
	utils.CommonData

	Timestamp string        `json:"timestamp"`
	NetNs     uint64        `json:"netns_id"`
	Proc      utils.Process `json:"proc"`

	Type string           `json:"type"`
	Src  utils.L4Endpoint `json:"src"`
	Dst  utils.L4Endpoint `json:"dst"`
}

func TestTraceTCPretrans(t *testing.T) {
	gadgettesting.RequireEnvironmentVariables(t)
	utils.InitTest(t)

	if utils.CurrentTestComponent == utils.KubectlGadgetTestComponent {
		t.Skip("Skipping test as K8s doesn't support privileged containers")
	} else if utils.CurrentTestComponent == utils.IgLocalTestComponent && utils.Runtime == "containerd" {
		t.Skip("Skipping test as containerd test utils can't use the network")
	}

	containerFactory, err := containers.NewContainerFactory(utils.Runtime)
	require.NoError(t, err, "new container factory")
	containerName := "test-trace-tcpretrans"
	containerImage := gadgettesting.NetworkMultitoolImage

	var ns string
	// run the container with privileged mode to be able to use tc command
	containerOpts := []containers.ContainerOption{containers.WithContainerImage(containerImage), containers.WithPrivileged()}

	if utils.CurrentTestComponent == utils.KubectlGadgetTestComponent {
		ns = utils.GenerateTestNamespaceName(t, "test-trace-tcpretrans")
		containerOpts = append(containerOpts, containers.WithContainerNamespace(ns))
	}
	// for testing purpose drop 30% of packets and wget 1.1.1.1 every 0.1 seconds
	cmds := "tc qdisc add dev eth0 root netem drop 30% && while true; do wget --no-check-certificate -q -O /dev/null https://1.1.1.1; sleep 0.1; done"
	testContainer := containerFactory.NewContainer(
		containerName,
		cmds,
		containerOpts...,
	)
	testContainer.Start(t)
	t.Cleanup(func() {
		testContainer.Stop(t)
	})
	var runnerOpts []igrunner.Option
	var testingOpts []igtesting.Option
	commonDataOpts := []utils.CommonDataOption{utils.WithContainerImageName(containerImage), utils.WithContainerID(testContainer.ID())}

	switch utils.CurrentTestComponent {
	case utils.IgLocalTestComponent:
		runnerOpts = append(runnerOpts, igrunner.WithFlags(fmt.Sprintf("-r=%s", utils.Runtime), "--timeout=5"))
	case utils.KubectlGadgetTestComponent:
		runnerOpts = append(runnerOpts, igrunner.WithFlags(fmt.Sprintf("-n=%s", ns), "--timeout=5"))
		testingOpts = append(testingOpts, igtesting.WithCbBeforeCleanup(utils.PrintLogsFn(ns)))
		commonDataOpts = append(commonDataOpts, utils.WithK8sNamespace(ns))
	}
	runnerOpts = append(runnerOpts, igrunner.WithValidateOutput(
		func(t *testing.T, output string) {
			expectedEntries := &traceTCPretransEvent{
				CommonData: utils.BuildCommonData(containerName, commonDataOpts...),
				Proc:       utils.BuildProc("wget", 0, 0),
				Src: utils.L4Endpoint{
					Addr:    utils.NormalizedStr,
					Version: 4,
					Port:    utils.NormalizedInt,
					Proto:   "TCP",
				},
				Dst: utils.L4Endpoint{
					Addr:    utils.NormalizedStr,
					Version: 4,
					Port:    443,
					Proto:   "TCP",
				},
				Type: "RETRANS",

				Timestamp: utils.NormalizedStr,
				NetNs:     utils.NormalizedInt,
			}

			if utils.CurrentTestComponent == utils.KubectlGadgetTestComponent {
				expectedEntries.Src.K8s = utils.K8s{
					Kind: "raw",
				}
				expectedEntries.Dst.K8s = expectedEntries.Src.K8s
			}

			normalize := func(e *traceTCPretransEvent) {
				utils.NormalizeCommonData(&e.CommonData)
				utils.NormalizeString(&e.Timestamp)
				utils.NormalizeInt(&e.NetNs)
				utils.NormalizeProc(&e.Proc)
				utils.NormalizeString(&e.Src.Addr)
				utils.NormalizeInt(&e.Src.Port)

				if e.Dst.Addr != "1.1.1.1" && e.Dst.Addr != "1.0.0.1" {
					t.Fatalf("unexpected destination address: %s", e.Dst.Addr)
				}
				utils.NormalizeString(&e.Dst.Addr)
			}
			match.MatchEntries(t, match.JSONMultiObjectMode, output, normalize, expectedEntries)
		},
	))
	traceTCPCmd := igrunner.New("trace_tcpretrans", runnerOpts...)

	igtesting.RunTestSteps([]igtesting.TestStep{traceTCPCmd}, t, testingOpts...)
}
