package hercules

import (
	"testing"

	"github.com/sergi/go-diff/diffmatchpatch"
	"github.com/stretchr/testify/assert"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/object"
	"unicode/utf8"
)

func fixtureFileDiff() *FileDiff {
	fd := &FileDiff{}
	fd.Initialize(testRepository)
	return fd
}

func TestFileDiffMeta(t *testing.T) {
	fd := fixtureFileDiff()
	assert.Equal(t, fd.Name(), "FileDiff")
	assert.Equal(t, len(fd.Provides()), 1)
	assert.Equal(t, fd.Provides()[0], DependencyFileDiff)
	assert.Equal(t, len(fd.Requires()), 2)
	assert.Equal(t, fd.Requires()[0], DependencyTreeChanges)
	assert.Equal(t, fd.Requires()[1], DependencyBlobCache)
	assert.Len(t, fd.ListConfigurationOptions(), 1)
	assert.Equal(t, fd.ListConfigurationOptions()[0].Name, ConfigFileDiffDisableCleanup)
	facts := map[string]interface{}{}
	facts[ConfigFileDiffDisableCleanup] = true
	fd.Configure(facts)
	assert.True(t, fd.CleanupDisabled)
}

func TestFileDiffRegistration(t *testing.T) {
	tp, exists := Registry.registered[(&FileDiff{}).Name()]
	assert.True(t, exists)
	assert.Equal(t, tp.Elem().Name(), "FileDiff")
	tps, exists := Registry.provided[(&FileDiff{}).Provides()[0]]
	assert.True(t, exists)
	assert.True(t, len(tps) >= 1)
	matched := false
	for _, tp := range tps {
		matched = matched || tp.Elem().Name() == "FileDiff"
	}
	assert.True(t, matched)
}

func TestFileDiffConsume(t *testing.T) {
	fd := fixtureFileDiff()
	deps := map[string]interface{}{}
	cache := map[plumbing.Hash]*object.Blob{}
	hash := plumbing.NewHash("291286b4ac41952cbd1389fda66420ec03c1a9fe")
	cache[hash], _ = testRepository.BlobObject(hash)
	hash = plumbing.NewHash("334cde09da4afcb74f8d2b3e6fd6cce61228b485")
	cache[hash], _ = testRepository.BlobObject(hash)
	hash = plumbing.NewHash("dc248ba2b22048cc730c571a748e8ffcf7085ab9")
	cache[hash], _ = testRepository.BlobObject(hash)
	deps[DependencyBlobCache] = cache
	changes := make(object.Changes, 3)
	treeFrom, _ := testRepository.TreeObject(plumbing.NewHash(
		"a1eb2ea76eb7f9bfbde9b243861474421000eb96"))
	treeTo, _ := testRepository.TreeObject(plumbing.NewHash(
		"994eac1cd07235bb9815e547a75c84265dea00f5"))
	changes[0] = &object.Change{From: object.ChangeEntry{
		Name: "analyser.go",
		Tree: treeFrom,
		TreeEntry: object.TreeEntry{
			Name: "analyser.go",
			Mode: 0100644,
			Hash: plumbing.NewHash("dc248ba2b22048cc730c571a748e8ffcf7085ab9"),
		},
	}, To: object.ChangeEntry{
		Name: "analyser.go",
		Tree: treeTo,
		TreeEntry: object.TreeEntry{
			Name: "analyser.go",
			Mode: 0100644,
			Hash: plumbing.NewHash("334cde09da4afcb74f8d2b3e6fd6cce61228b485"),
		},
	}}
	changes[1] = &object.Change{From: object.ChangeEntry{}, To: object.ChangeEntry{
		Name: ".travis.yml",
		Tree: treeTo,
		TreeEntry: object.TreeEntry{
			Name: ".travis.yml",
			Mode: 0100644,
			Hash: plumbing.NewHash("291286b4ac41952cbd1389fda66420ec03c1a9fe"),
		},
	},
	}
	changes[2] = &object.Change{From: object.ChangeEntry{
		Name: "rbtree.go",
		Tree: treeFrom,
		TreeEntry: object.TreeEntry{
			Name: "rbtree.go",
			Mode: 0100644,
			Hash: plumbing.NewHash("14c3fa5a1cca103032f10379467a3a2f210e5f94"),
		},
	}, To: object.ChangeEntry{},
	}
	deps[DependencyTreeChanges] = changes
	res, err := fd.Consume(deps)
	assert.Nil(t, err)
	diffs := res[DependencyFileDiff].(map[string]FileDiffData)
	assert.Equal(t, len(diffs), 1)
	diff := diffs["analyser.go"]
	assert.Equal(t, diff.OldLinesOfCode, 307)
	assert.Equal(t, diff.NewLinesOfCode, 309)
	deletions := 0
	insertions := 0
	for _, edit := range diff.Diffs {
		switch edit.Type {
		case diffmatchpatch.DiffEqual:
			continue
		case diffmatchpatch.DiffInsert:
			insertions += utf8.RuneCountInString(edit.Text)
		case diffmatchpatch.DiffDelete:
			deletions += utf8.RuneCountInString(edit.Text)
		}
	}
	assert.Equal(t, deletions, 13)
	assert.Equal(t, insertions, 15)
}

func TestFileDiffConsumeInvalidBlob(t *testing.T) {
	fd := fixtureFileDiff()
	deps := map[string]interface{}{}
	cache := map[plumbing.Hash]*object.Blob{}
	hash := plumbing.NewHash("291286b4ac41952cbd1389fda66420ec03c1a9fe")
	cache[hash], _ = testRepository.BlobObject(hash)
	hash = plumbing.NewHash("334cde09da4afcb74f8d2b3e6fd6cce61228b485")
	cache[hash], _ = testRepository.BlobObject(hash)
	hash = plumbing.NewHash("dc248ba2b22048cc730c571a748e8ffcf7085ab9")
	cache[hash], _ = testRepository.BlobObject(hash)
	deps[DependencyBlobCache] = cache
	changes := make(object.Changes, 1)
	treeFrom, _ := testRepository.TreeObject(plumbing.NewHash(
		"a1eb2ea76eb7f9bfbde9b243861474421000eb96"))
	treeTo, _ := testRepository.TreeObject(plumbing.NewHash(
		"994eac1cd07235bb9815e547a75c84265dea00f5"))
	changes[0] = &object.Change{From: object.ChangeEntry{
		Name: "analyser.go",
		Tree: treeFrom,
		TreeEntry: object.TreeEntry{
			Name: "analyser.go",
			Mode: 0100644,
			Hash: plumbing.NewHash("ffffffffffffffffffffffffffffffffffffffff"),
		},
	}, To: object.ChangeEntry{
		Name: "analyser.go",
		Tree: treeTo,
		TreeEntry: object.TreeEntry{
			Name: "analyser.go",
			Mode: 0100644,
			Hash: plumbing.NewHash("334cde09da4afcb74f8d2b3e6fd6cce61228b485"),
		},
	}}
	deps[DependencyTreeChanges] = changes
	res, err := fd.Consume(deps)
	assert.Nil(t, res)
	assert.NotNil(t, err)
	changes[0] = &object.Change{From: object.ChangeEntry{
		Name: "analyser.go",
		Tree: treeFrom,
		TreeEntry: object.TreeEntry{
			Name: "analyser.go",
			Mode: 0100644,
			Hash: plumbing.NewHash("dc248ba2b22048cc730c571a748e8ffcf7085ab9"),
		},
	}, To: object.ChangeEntry{
		Name: "analyser.go",
		Tree: treeTo,
		TreeEntry: object.TreeEntry{
			Name: "analyser.go",
			Mode: 0100644,
			Hash: plumbing.NewHash("ffffffffffffffffffffffffffffffffffffffff"),
		},
	}}
	res, err = fd.Consume(deps)
	assert.Nil(t, res)
	assert.NotNil(t, err)
}

func TestCountLines(t *testing.T) {
	blob, _ := testRepository.BlobObject(
		plumbing.NewHash("291286b4ac41952cbd1389fda66420ec03c1a9fe"))
	lines, err := CountLines(blob)
	assert.Equal(t, lines, 12)
	assert.Nil(t, err)
	lines, err = CountLines(nil)
	assert.Equal(t, lines, -1)
	assert.NotNil(t, err)
	blob, _ = createDummyBlob(plumbing.NewHash("291286b4ac41952cbd1389fda66420ec03c1a9fe"), true)
	lines, err = CountLines(blob)
	assert.Equal(t, lines, -1)
	assert.NotNil(t, err)
	// test_data/blob
	blob, err = testRepository.BlobObject(
		plumbing.NewHash("c86626638e0bc8cf47ca49bb1525b40e9737ee64"))
	assert.Nil(t, err)
	lines, err = CountLines(blob)
	assert.Equal(t, lines, -1)
	assert.NotNil(t, err)
	assert.EqualError(t, err, "binary")
}

func TestBlobToString(t *testing.T) {
	blob, _ := testRepository.BlobObject(
		plumbing.NewHash("291286b4ac41952cbd1389fda66420ec03c1a9fe"))
	str, err := BlobToString(blob)
	assert.Nil(t, err)
	assert.Equal(t, str, `language: go

go:
  - 1.7

go_import_path: gopkg.in/src-d/hercules.v1
`+"  "+`
script:
  - go test -v -cpu=1,2 ./...

notifications:
  email: false
`)
	str, err = BlobToString(nil)
	assert.Equal(t, str, "")
	assert.NotNil(t, err)
	blob, _ = createDummyBlob(plumbing.NewHash("291286b4ac41952cbd1389fda66420ec03c1a9fe"), true)
	str, err = BlobToString(blob)
	assert.Equal(t, str, "")
	assert.NotNil(t, err)
}

func TestFileDiffDarkMagic(t *testing.T) {
	fd := fixtureFileDiff()
	deps := map[string]interface{}{}
	cache := map[plumbing.Hash]*object.Blob{}
	hash := plumbing.NewHash("448eb3f312849b0ca766063d06b09481c987b309")
	cache[hash], _ = testRepository.BlobObject(hash) // 1.java
	hash = plumbing.NewHash("3312c92f3e8bdfbbdb30bccb6acd1b85bc338dfc")
	cache[hash], _ = testRepository.BlobObject(hash) // 2.java
	deps[DependencyBlobCache] = cache
	changes := make(object.Changes, 1)
	treeFrom, _ := testRepository.TreeObject(plumbing.NewHash(
		"f02289bfe843388a1bb3c7dea210374082dd86b9"))
	treeTo, _ := testRepository.TreeObject(plumbing.NewHash(
		"eca91acf1fd828f20dcb653a061d8c97d965bc6c"))
	changes[0] = &object.Change{From: object.ChangeEntry{
		Name: "test.java",
		Tree: treeFrom,
		TreeEntry: object.TreeEntry{
			Name: "test.java",
			Mode: 0100644,
			Hash: plumbing.NewHash("448eb3f312849b0ca766063d06b09481c987b309"),
		},
	}, To: object.ChangeEntry{
		Name: "test.java",
		Tree: treeTo,
		TreeEntry: object.TreeEntry{
			Name: "test.java",
			Mode: 0100644,
			Hash: plumbing.NewHash("3312c92f3e8bdfbbdb30bccb6acd1b85bc338dfc"),
		},
	}}
	deps[DependencyTreeChanges] = changes
	res, err := fd.Consume(deps)
	assert.Nil(t, err)
	magicDiffs := res[DependencyFileDiff].(map[string]FileDiffData)["test.java"]
	fd.CleanupDisabled = true
	res, err = fd.Consume(deps)
	assert.Nil(t, err)
	plainDiffs := res[DependencyFileDiff].(map[string]FileDiffData)["test.java"]
	assert.NotEqual(t, magicDiffs.Diffs, plainDiffs.Diffs)
	assert.Equal(t, magicDiffs.OldLinesOfCode, plainDiffs.OldLinesOfCode)
	assert.Equal(t, magicDiffs.NewLinesOfCode, plainDiffs.NewLinesOfCode)
}
