package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"testing"

	"v.io/jiri/gitutil"
	"v.io/jiri/jiritest"
	"v.io/jiri/project"
	"v.io/jiri/tool"
)

func TestCopyright(t *testing.T) {
	fake, cleanup := jiritest.NewFakeJiriRoot(t)
	defer cleanup()

	var errOut bytes.Buffer
	fake.X.Context = tool.NewContext(tool.ContextOpts{
		Stderr: io.MultiWriter(os.Stderr, &errOut),
	})

	// Load assets.
	assets, err := loadAssets(fake.X, filepath.Join("..", "tooldata", "data"))
	if err != nil {
		t.Fatalf("%v", err)
	}

	if err := fake.CreateRemoteProject("test"); err != nil {
		t.Fatalf("%v", err)
	}
	if err := fake.AddProject(project.Project{
		Name:   "test",
		Path:   "test",
		Remote: fake.Projects["test"],
	}); err != nil {
		t.Fatalf("%v", err)
	}
	if err := fake.UpdateUniverse(false); err != nil {
		t.Fatalf("%v", err)
	}

	allFiles := map[string]string{}
	for file, data := range assets.MatchFiles {
		allFiles[file] = data
	}
	for file, data := range assets.MatchPrefixFiles {
		allFiles[file] = data
	}

	// Write out test licensing files and sample source code files to a
	// project and verify that the project checks out.
	projectPath := filepath.Join(fake.X.Root, "test")
	project := project.Project{Path: projectPath}
	s := fake.X.NewSeq()
	for _, lang := range languages {
		file := "test" + lang.FileExtension
		if err := s.WriteFile(filepath.Join(projectPath, file), nil, os.FileMode(0600)).Done(); err != nil {
			t.Fatalf("%v", err)
		}
		missing, err := checkFile(fake.X, filepath.Join(project.Path, file), assets, true)
		if err != nil {
			t.Fatalf("%v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		if err := gitutil.New(fake.X.NewSeq(), gitutil.RootDirOpt(projectPath)).CommitFile(file, "adding "+file); err != nil {
			t.Fatalf("%v", err)
		}
	}
	for file, data := range allFiles {
		if err := s.WriteFile(filepath.Join(projectPath, file), []byte(data), os.FileMode(0600)).Done(); err != nil {
			t.Fatalf("%v", err)
		}
		if err := gitutil.New(fake.X.NewSeq(), gitutil.RootDirOpt(projectPath)).CommitFile(file, "adding "+file); err != nil {
			t.Fatalf("%v", err)
		}
	}
	missing, err := checkProject(fake.X, project, assets, false)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got, want := missing, false; got != want {
		t.Errorf("got %v, want %v", got, want)
	}
	if got, want := errOut.String(), ""; got != want {
		t.Fatalf("unexpected error message: got %q, want %q", got, want)
	}
	// Check that missing licensing files are reported correctly.
	for file, _ := range allFiles {
		errOut.Reset()
		missing, err := checkProject(fake.X, project, assets, true)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		path := filepath.Join(projectPath, file)
		if err := gitutil.New(fake.X.NewSeq(), gitutil.RootDirOpt(projectPath)).Remove(file); err != nil {
			t.Fatalf("%v", err)
		}
		missing, err = checkProject(fake.X, project, assets, false)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, true; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		if got, want := errOut.String(), fmt.Sprintf("%v is missing\n", path); got != want {
			t.Fatalf("unexpected error message: got %q, want %q", got, want)
		}
	}
	// Check that out-of-date licensing files are reported correctly.
	for file, _ := range allFiles {
		errOut.Reset()
		missing, err := checkProject(fake.X, project, assets, true)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		path := filepath.Join(projectPath, file)
		if err := s.WriteFile(path, []byte("garbage"), os.FileMode(0600)).Done(); err != nil {
			t.Fatalf("%v", err)
		}
		missing, err = checkProject(fake.X, project, assets, false)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, true; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		if got, want := errOut.String(), fmt.Sprintf("%v is not up-to-date\n", path); got != want {
			t.Fatalf("unexpected error message: got %q, want %q", got, want)
		}
	}

	// Check that source code files without the copyright header are
	// reported correctly.
	for _, lang := range languages {
		errOut.Reset()
		missing, err := checkProject(fake.X, project, assets, true)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		path := filepath.Join(projectPath, "test"+lang.FileExtension)
		if err := s.WriteFile(path, []byte("garbage"), os.FileMode(0600)).Done(); err != nil {
			t.Fatalf("%v", err)
		}
		missing, err = checkProject(fake.X, project, assets, false)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, true; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		if got, want := errOut.String(), fmt.Sprintf("%v copyright is missing\n", path); got != want {
			t.Fatalf("unexpected error message: got %q, want %q", got, want)
		}
	}

	// Check that missing licensing files are fixed up correctly.
	for file, _ := range allFiles {
		errOut.Reset()
		missing, err := checkProject(fake.X, project, assets, true)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		path := filepath.Join(projectPath, file)
		if err := s.RemoveAll(path).Done(); err != nil {
			t.Fatalf("%v", err)
		}
		missing, err = checkProject(fake.X, project, assets, true)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		missing, err = checkProject(fake.X, project, assets, false)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		if got, want := errOut.String(), ""; got != want {
			t.Fatalf("unexpected error message: got %q, want %q", got, want)
		}
	}

	// Check that out-of-date licensing files are fixed up correctly.
	for file, _ := range allFiles {
		errOut.Reset()
		missing, err := checkProject(fake.X, project, assets, true)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		path := filepath.Join(projectPath, file)
		if err := s.WriteFile(path, []byte("garbage"), os.FileMode(0600)).Done(); err != nil {
			t.Fatalf("%v", err)
		}
		missing, err = checkProject(fake.X, project, assets, true)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		missing, err = checkProject(fake.X, project, assets, false)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		if got, want := errOut.String(), ""; got != want {
			t.Fatalf("unexpected error message: got %q, want %q", got, want)
		}
	}

	// Check that source code files missing the copyright header are
	// fixed up correctly.
	for _, lang := range languages {
		errOut.Reset()
		missing, err := checkProject(fake.X, project, assets, true)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		path := filepath.Join(projectPath, "test"+lang.FileExtension)
		if err := s.WriteFile(path, []byte("garbage"), os.FileMode(0600)).Done(); err != nil {
			t.Fatalf("%v", err)
		}
		missing, err = checkProject(fake.X, project, assets, true)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		missing, err = checkProject(fake.X, project, assets, false)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got, want := missing, false; got != want {
			t.Errorf("got %v, want %v", got, want)
		}
		if got, want := errOut.String(), ""; got != want {
			t.Fatalf("unexpected error message: got %q, want %q", got, want)
		}
	}

	// Check that third-party files are skipped when checking for copyright
	// headers.
	errOut.Reset()
	missing, err = checkProject(fake.X, project, assets, true)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got, want := missing, false; got != want {
		t.Errorf("got %v, want %v", got, want)
	}
	path := filepath.Join(projectPath, "third_party")
	gpath := filepath.Join(path, "test.go")
	if err := s.MkdirAll(path, 0700).
		WriteFile(gpath, []byte("garbage"), os.FileMode(0600)).Done(); err != nil {
		t.Fatalf("%v", err)
	}
	// Since this file is in a subdir, we must run "git add" to have git track it.
	// Without this, the test passes regardless of the subdir name.
	if err := gitutil.New(fake.X.NewSeq(), gitutil.RootDirOpt(projectPath)).Add(path); err != nil {
		t.Fatalf("%v", err)
	}
	missing, err = checkProject(fake.X, project, assets, false)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got, want := missing, false; got != want {
		t.Errorf("got %v, want %v", got, want)
	}
	if errOut.Len() != 0 {
		t.Fatalf("unexpected error message: %q", errOut.String())
	}

	// Test .jiriignore functionality.
	errOut.Reset()
	// Add .jiriignore file.
	ignoreFile := filepath.Join(projectPath, jiriIgnore)
	publicDir := filepath.Join(projectPath, "public")
	filename := filepath.Join(publicDir, "fancy.js")

	if err := s.
		WriteFile(ignoreFile, []byte("public/fancy.js"), os.FileMode(0600)).
		MkdirAll(publicDir, 0700).
		WriteFile(filename, []byte("garbage"), os.FileMode(0600)).Done(); err != nil {
		t.Fatalf("%v", err)
	}

	// Since the copyright check only applies to tracked files, we must run "git
	// add" to have git track it. Without this, the test passes regardless of the
	// subdir name.
	if err := gitutil.New(fake.X.NewSeq(), gitutil.RootDirOpt(projectPath)).Add(filename); err != nil {
		t.Fatalf("%v", err)
	}
	missing, err = checkProject(fake.X, project, assets, false)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got, want := missing, false; got != want {
		t.Errorf("got %v, want %v", got, want)
	}
	if errOut.Len() != 0 {
		t.Fatalf("unexpected error message: %q", errOut.String())
	}
}

func TestCopyrightIsIgnored(t *testing.T) {
	lines := []string{
		"public/bundle.*",
		"build/",
		"dist/min.js",
	}
	expressions := []*regexp.Regexp{}
	for _, line := range lines {
		expression, err := regexp.Compile(line)
		if err != nil {
			t.Fatalf("unexpected regexp.Compile(%v) failed: %v", line, err)
		}

		expressions = append(expressions, expression)
	}

	shouldIgnore := []string{
		"public/bundle.js",
		"public/bundle.css",
		"dist/min.js",
		"build/bar",
	}

	for _, path := range shouldIgnore {
		if ignore, _ := isIgnored(path, expressions); !ignore {
			t.Errorf("isIgnored(%s, expressions) == %v, should be %v", path, ignore, true)
		}
	}

	shouldNotIgnore := []string{"foo", "bar"}
	for _, path := range shouldNotIgnore {
		if ignore, _ := isIgnored(path, expressions); ignore {
			t.Errorf("isIgnored(%s, expressions) == %v, should be %v", path, ignore, false)
		}
	}
}
