package gosec_test

import (
	"go/ast"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"

	"github.com/securego/gosec/v2"
	"github.com/securego/gosec/v2/testutils"
)

var _ = Describe("Call List", func() {
	var calls gosec.CallList
	BeforeEach(func() {
		calls = gosec.NewCallList()
	})

	It("should not return any matches when empty", func() {
		Expect(calls.Contains("foo", "bar")).Should(BeFalse())
	})

	It("should be possible to add a single call", func() {
		Expect(calls).Should(BeEmpty())
		calls.Add("foo", "bar")
		Expect(calls).Should(HaveLen(1))

		expected := make(map[string]bool)
		expected["bar"] = true
		actual := map[string]bool(calls["foo"])
		Expect(actual).Should(Equal(expected))
	})

	It("should be possible to add multiple calls at once", func() {
		Expect(calls).Should(BeEmpty())
		calls.AddAll("fmt", "Sprint", "Sprintf", "Printf", "Println")

		expected := map[string]bool{
			"Sprint":  true,
			"Sprintf": true,
			"Printf":  true,
			"Println": true,
		}
		actual := map[string]bool(calls["fmt"])
		Expect(actual).Should(Equal(expected))
	})

	It("should be possible to add pointer call", func() {
		Expect(calls).Should(BeEmpty())
		calls.Add("*bytes.Buffer", "WriteString")
		actual := calls.ContainsPointer("*bytes.Buffer", "WriteString")
		Expect(actual).Should(BeTrue())
	})

	It("should be possible to check pointer call", func() {
		Expect(calls).Should(BeEmpty())
		calls.Add("bytes.Buffer", "WriteString")
		actual := calls.ContainsPointer("*bytes.Buffer", "WriteString")
		Expect(actual).Should(BeTrue())
	})

	It("should not return a match if none are present", func() {
		calls.Add("ioutil", "Copy")
		Expect(calls.Contains("fmt", "Println")).Should(BeFalse())
	})

	It("should match a call based on selector and ident", func() {
		calls.Add("ioutil", "Copy")
		Expect(calls.Contains("ioutil", "Copy")).Should(BeTrue())
	})

	It("should match a package call expression", func() {
		// Create file to be scanned
		pkg := testutils.NewTestPackage()
		defer pkg.Close()
		pkg.AddFile("md5.go", testutils.SampleCodeG401[0].Code[0])

		ctx := pkg.CreateContext("md5.go")

		// Search for md5.New()
		calls.Add("crypto/md5", "New")

		// Stub out visitor and count number of matched call expr
		matched := 0
		v := testutils.NewMockVisitor()
		v.Context = ctx
		v.Callback = func(n ast.Node, ctx *gosec.Context) bool {
			if _, ok := n.(*ast.CallExpr); ok && calls.ContainsPkgCallExpr(n, ctx, false) != nil {
				matched++
			}
			return true
		}
		ast.Walk(v, ctx.Root)
		Expect(matched).Should(Equal(1))
	})

	It("should match a call expression", func() {
		// Create file to be scanned
		pkg := testutils.NewTestPackage()
		defer pkg.Close()
		pkg.AddFile("main.go", testutils.SampleCodeG104[6].Code[0])

		ctx := pkg.CreateContext("main.go")

		calls.Add("bytes.Buffer", "WriteString")
		calls.Add("strings.Builder", "WriteString")
		calls.Add("io.Pipe", "CloseWithError")
		calls.Add("fmt", "Fprintln")

		// Stub out visitor and count number of matched call expr
		matched := 0
		v := testutils.NewMockVisitor()
		v.Context = ctx
		v.Callback = func(n ast.Node, ctx *gosec.Context) bool {
			if _, ok := n.(*ast.CallExpr); ok && calls.ContainsCallExpr(n, ctx) != nil {
				matched++
			}
			return true
		}
		ast.Walk(v, ctx.Root)
		Expect(matched).Should(Equal(5))
	})
})