// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cache

import (
	"os"
	"testing"

	"github.com/google/go-cmp/cmp"
	"golang.org/x/sync/errgroup"
	"golang.org/x/tools/go/packages"
	"golang.org/x/tools/gopls/internal/file"
	"golang.org/x/tools/gopls/internal/protocol"
	"golang.org/x/tools/gopls/internal/util/bug"
	"golang.org/x/tools/internal/testenv"
)

func TestMain(m *testing.M) {
	bug.PanicOnBugs = true
	os.Exit(m.Run())
}

func TestMatchingPortsStdlib(t *testing.T) {
	// This test checks that we don't encounter a bug when matching ports, and
	// sanity checks that the optimization to use trimmed/fake file content
	// before delegating to go/build.Context.MatchFile does not affect
	// correctness.
	if testing.Short() {
		t.Skip("skipping in short mode: takes to long on slow file systems")
	}

	testenv.NeedsTool(t, "go")

	// Load, parse and type-check the program.
	cfg := &packages.Config{
		Mode:  packages.LoadFiles,
		Tests: true,
	}
	pkgs, err := packages.Load(cfg, "std", "cmd")
	if err != nil {
		t.Fatal(err)
	}

	var g errgroup.Group
	packages.Visit(pkgs, nil, func(pkg *packages.Package) {
		for _, f := range pkg.CompiledGoFiles {
			g.Go(func() error {
				content, err := os.ReadFile(f)
				// We report errors via t.Error, not by returning,
				// so that a single test can report multiple test failures.
				if err != nil {
					t.Errorf("failed to read %s: %v", f, err)
					return nil
				}
				fh := makeFakeFileHandle(protocol.URIFromPath(f), content)
				fastPorts := matchingPreferredPorts(t, fh, true)
				slowPorts := matchingPreferredPorts(t, fh, false)
				if diff := cmp.Diff(fastPorts, slowPorts); diff != "" {
					t.Errorf("%s: ports do not match (-trimmed +untrimmed):\n%s", f, diff)
					return nil
				}
				return nil
			})
		}
	})
	_ = g.Wait() // can't fail
}

func matchingPreferredPorts(tb testing.TB, fh file.Handle, trimContent bool) map[port]unit {
	content, err := fh.Content()
	if err != nil {
		tb.Fatal(err)
	}
	if trimContent {
		content = trimContentForPortMatch(content)
	}
	path := fh.URI().Path()
	matching := make(map[port]unit)
	for _, port := range preferredPorts {
		if port.matches(path, content) {
			matching[port] = unit{}
		}
	}
	return matching
}

func BenchmarkMatchingPreferredPorts(b *testing.B) {
	// Copy of robustio_posix.go
	const src = `
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build unix
// +build unix

package robustio

import (
	"os"
	"syscall"
	"time"
)

func getFileID(filename string) (FileID, time.Time, error) {
	fi, err := os.Stat(filename)
	if err != nil {
		return FileID{}, time.Time{}, err
	}
	stat := fi.Sys().(*syscall.Stat_t)
	return FileID{
		device: uint64(stat.Dev), // (int32 on darwin, uint64 on linux)
		inode:  stat.Ino,
	}, fi.ModTime(), nil
}
`
	fh := makeFakeFileHandle("file:///path/to/test/file.go", []byte(src))
	for b.Loop() {
		_ = matchingPreferredPorts(b, fh, true)
	}
}