package agent

import (
	"bytes"
	"context"
	"io"
	"net/http"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/modagent"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/mock_modagent"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/testlogger"
	"gitlab.com/gitlab-org/security-products/analyzers/trivy-k8s-wrapper/data/analyzers/report"
	"go.uber.org/mock/gomock"
)

var payloads = []*Payload{{
	Scanner: TrivyScanner,
	Vulnerability: &report.Vulnerability{
		Name:        "CVE-2020-27350",
		Message:     "CVE-2020-27350 in apt",
		Description: "apt: integer overflows and underflows while parsing .deb packages",
		Solution:    "Upgrade apt from 1.8.2 to 1.8.2.2",
		Severity:    report.SeverityLevelMedium,
		Confidence:  report.ConfidenceLevelUnknown,
		Identifiers: []report.Identifier{
			{
				Type:  report.IdentifierTypeCVE,
				Name:  "CVE-2020-27350",
				Value: "CVE-2020-27350",
				URL:   "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-27350",
			},
		},
		Links: []report.Link{{URL: "https://avd.aquasec.com/nvd/cve-2020-27350"}},
	},
}}

func TestCreateVulnerability(t *testing.T) {
	ctrl := gomock.NewController(t)
	api := mock_modagent.NewMockAPI(ctrl)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	api.EXPECT().MakeGitLabRequest(ctx, "/",
		gomock.Any(),
		gomock.Any(),
	).Times(1).
		DoAndReturn(func(ctx context.Context, path string, opts ...modagent.GitLabRequestOption) (*modagent.GitLabResponse, error) {
			cancel()

			return &modagent.GitLabResponse{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(strings.NewReader(`{"uuid": "ed1da145-ed9c-4d4c-adbf-6b9f8a305745"}`)),
			}, nil
		})

	reporter := newReporter(testlogger.New(t), api)
	uuid, err := reporter.createVulnerability(ctx, payloads[0])
	require.NoError(t, err)
	assert.Equal(t, "ed1da145-ed9c-4d4c-adbf-6b9f8a305745", uuid)
}

func TestCreateVulnerabilityEmptyResponseBody(t *testing.T) {
	ctrl := gomock.NewController(t)
	api := mock_modagent.NewMockAPI(ctrl)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	api.EXPECT().MakeGitLabRequest(ctx, "/",
		gomock.Any(),
		gomock.Any(),
	).Times(1).
		DoAndReturn(func(ctx context.Context, path string, opts ...modagent.GitLabRequestOption) (*modagent.GitLabResponse, error) {
			cancel()

			return &modagent.GitLabResponse{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(strings.NewReader("")),
			}, nil
		})

	reporter := newReporter(testlogger.New(t), api)
	uuid, err := reporter.createVulnerability(ctx, payloads[0])
	require.Error(t, err)
	assert.Empty(t, uuid)
}

func TestResolveVulnerabilities(t *testing.T) {
	ctrl := gomock.NewController(t)
	api := mock_modagent.NewMockAPI(ctrl)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	api.EXPECT().MakeGitLabRequest(ctx, "/scan_result",
		gomock.Any(),
		gomock.Any(),
	).Times(1).
		DoAndReturn(func(ctx context.Context, path string, opts ...modagent.GitLabRequestOption) (*modagent.GitLabResponse, error) {
			cancel()

			return &modagent.GitLabResponse{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(bytes.NewReader(nil)),
			}, nil
		})

	reporter := newReporter(testlogger.New(t), api)
	err := reporter.ResolveVulnerabilities(ctx, []string{"586444af-3561-4b71-8cfe-4e5570c0c114", "7cc3a693-d616-4cfc-beef-3a391b3d0348"})
	require.NoError(t, err)
}

func TestTransmit(t *testing.T) {
	ctrl := gomock.NewController(t)
	api := mock_modagent.NewMockAPI(ctrl)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	api.EXPECT().MakeGitLabRequest(ctx, "/",
		gomock.Any(),
		gomock.Any(),
	).DoAndReturn(func(ctx context.Context, path string, opts ...modagent.GitLabRequestOption) (*modagent.GitLabResponse, error) {
		cancel()

		return &modagent.GitLabResponse{
			StatusCode: http.StatusOK,
			Body:       io.NopCloser(strings.NewReader(`{"uuid": "ed1da145-ed9c-4d4c-adbf-6b9f8a305745"}`)),
		}, nil
	})

	reporter := newReporter(testlogger.New(t), api)
	uuids, err := reporter.Transmit(ctx, payloads)
	require.NoError(t, err)
	assert.Equal(t, []string{"ed1da145-ed9c-4d4c-adbf-6b9f8a305745"}, uuids)
}

func TestTransmitEmptyResponseBody(t *testing.T) {
	ctrl := gomock.NewController(t)
	api := mock_modagent.NewMockAPI(ctrl)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	api.EXPECT().MakeGitLabRequest(ctx, "/",
		gomock.Any(),
		gomock.Any(),
	).Times(1).
		DoAndReturn(func(ctx context.Context, path string, opts ...modagent.GitLabRequestOption) (*modagent.GitLabResponse, error) {
			cancel()

			return &modagent.GitLabResponse{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(strings.NewReader("")),
			}, nil
		})

	reporter := newReporter(testlogger.New(t), api)
	uuids, err := reporter.Transmit(ctx, payloads)
	require.Error(t, err)
	assert.Empty(t, uuids)
}

func TestTransmitBadRequestResponse(t *testing.T) {
	ctrl := gomock.NewController(t)
	api := mock_modagent.NewMockAPI(ctrl)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	api.EXPECT().MakeGitLabRequest(ctx, "/",
		gomock.Any(),
		gomock.Any(),
	).Times(1).
		DoAndReturn(func(ctx context.Context, path string, opts ...modagent.GitLabRequestOption) (*modagent.GitLabResponse, error) {
			cancel()

			return &modagent.GitLabResponse{
				StatusCode: http.StatusBadRequest,
				Body:       io.NopCloser(strings.NewReader(`{"error": "scanner is missing, scanner[id] is missing, scanner[name] is missing, scanner[vendor] is missing, scanner[vendor][name] is missing"}`)),
			}, nil
		})

	reporter := newReporter(testlogger.New(t), api)
	uuids, err := reporter.Transmit(ctx, payloads)
	require.Error(t, err)
	require.ErrorContains(t, err, "scanner is missing, scanner[id] is missing, scanner[name] is missing, scanner[vendor] is missing, scanner[vendor][name] is missing")
	assert.Empty(t, uuids)
}
