package log_merger

import (
	"context"
	"io/ioutil"
	"os"
	"testing"

	"github.com/pingcap-inc/alioth/pkg/util"
	logpattern_go_proto "github.com/pingcap-inc/alioth/proto"
	"github.com/pingcap-inc/alioth/storage"
	. "github.com/pingcap/check"
)

func TestClient(t *testing.T) {
	TestingT(t)
}

var _ = Suite(&testMergeSuite{})

type testMergeSuite struct {
}

func (t *testMergeSuite) TestCoverageMerge(c *C) {
	tmpdir1, err := ioutil.TempDir("./", "logpattern_test")
	c.Assert(err, IsNil)
	defer os.RemoveAll(tmpdir1)

	tmpdir2, err := ioutil.TempDir("./", "logpattern_test")
	c.Assert(err, IsNil)
	defer os.RemoveAll(tmpdir2)

	tmpdir3, err := ioutil.TempDir("./", "logpattern_test")
	c.Assert(err, IsNil)
	defer os.RemoveAll(tmpdir3)

	merger, err := NewMerger([]string{tmpdir1, tmpdir2}, tmpdir3)
	c.Assert(err, IsNil)

	store1 := merger.sources[tmpdir1].store
	store2 := merger.sources[tmpdir2].store

	casses := t.testMockStoreA(c, store1)

	c.Assert(merger.Merge(context.Background()), IsNil)

	result := make(map[string]map[string]int32)
	err = merger.target.store.ScanLogPattern(context.Background(), func(_, value []byte) error {
		pattern := &logpattern_go_proto.LogPattern{}
		c.Assert(pattern.Unmarshal(value), IsNil)
		result[util.PosToStr(pattern.Pos)] = make(map[string]int32)
		return nil
	})
	// len(cases) -1 : one for unknow log
	c.Assert(len(result), Equals, len(casses)-1)

	err = merger.target.store.ScanLogCoverage(context.Background(), func(_, value []byte) error {
		coverage := &logpattern_go_proto.Coverage{}
		c.Assert(coverage.Unmarshal(value), IsNil)
		cov := result[util.PosToStr(coverage.Pos)]
		c.Assert(cov, NotNil)
		cov["total"] = coverage.CovCount
		for file, count := range coverage.CovCountByLog {
			cov[file] = count
		}
		return nil
	})
	err = merger.target.store.ScanUnknownLog(context.Background(), func(_, value []byte) error {
		unknowLog := &logpattern_go_proto.UnknowLogPattern{}
		c.Assert(unknowLog.Unmarshal(value), IsNil)
		c.Assert(unknowLog.Message, Equals, "test")
		c.Assert(unknowLog.Level, Equals, "error")
		cov := result[util.PosToStr(unknowLog.Pos)]
		c.Assert(cov, IsNil)
		result[util.PosToStr(unknowLog.Pos)] = make(map[string]int32)
		cov = result[util.PosToStr(unknowLog.Pos)]
		cov["total"] = unknowLog.CovCount
		for file, count := range unknowLog.CovCountByLog {
			cov[file] = count
		}
		return nil
	})
	c.Assert(result, DeepEquals, casses)

	cases2 := t.testMockStoreB(c, store2)
	c.Assert(merger.Merge(context.Background()), IsNil)

	result = make(map[string]map[string]int32)
	err = merger.target.store.ScanLogPattern(context.Background(), func(_, value []byte) error {
		pattern := &logpattern_go_proto.LogPattern{}
		c.Assert(pattern.Unmarshal(value), IsNil)
		result[util.PosToStr(pattern.Pos)] = make(map[string]int32)
		return nil
	})
	err = merger.target.store.ScanLogCoverage(context.Background(), func(_, value []byte) error {
		coverage := &logpattern_go_proto.Coverage{}
		c.Assert(coverage.Unmarshal(value), IsNil)
		cov := result[util.PosToStr(coverage.Pos)]
		c.Assert(cov, NotNil)
		cov["total"] = coverage.CovCount
		for file, count := range coverage.CovCountByLog {
			cov[file] = count
		}
		return nil
	})
	err = merger.target.store.ScanUnknownLog(context.Background(), func(_, value []byte) error {
		unknowLog := &logpattern_go_proto.UnknowLogPattern{}
		c.Assert(unknowLog.Unmarshal(value), IsNil)
		c.Assert(unknowLog.Message, Equals, "test")
		c.Assert(unknowLog.Level, Equals, "error")
		cov := result[util.PosToStr(unknowLog.Pos)]
		c.Assert(cov, IsNil)
		result[util.PosToStr(unknowLog.Pos)] = make(map[string]int32)
		cov = result[util.PosToStr(unknowLog.Pos)]
		cov["total"] = unknowLog.CovCount
		for file, count := range unknowLog.CovCountByLog {
			cov[file] = count
		}
		return nil
	})
	c.Assert(result, DeepEquals, cases2)

}

func (t *testMergeSuite) testMockStoreB(c *C, store *storage.LogPatternStore) map[string]map[string]int32 {
	cases := make(map[string]map[string]int32)

	pattern := &logpattern_go_proto.LogPattern{
		Pos: &logpattern_go_proto.Position{
			PackageInfo: &logpattern_go_proto.Package{
				Repo: "github.com/pingcap/ticdc/dm",
			},
			FilePath: "dm/dm/master3/config.go",
			Pos: &logpattern_go_proto.CodePosition{
				Line:   154,
				Column: 7274,
			},
		},
		Func: &logpattern_go_proto.FuncInfo{
			Name: "Toml",
			Pos: &logpattern_go_proto.Position{
				FilePath: "dm/dm/master/config.go",
				Pos: &logpattern_go_proto.CodePosition{
					Line:   138,
					Column: 7140,
				},
			},
		},
		Level:     "error",
		Signature: []string{"\"fail to marshal config to % wwwww"},
	}

	err := store.WriteLogPattern(context.Background(), pattern)
	c.Assert(err, IsNil)

	coverage := &logpattern_go_proto.Coverage{
		Pos:      pattern.Pos,
		CovCount: 3,
		CovCountByLog: map[string]int32{
			"file1": 1,
			"file2": 2,
		},
	}
	err = store.WriteLogCoverage(context.Background(), coverage)
	c.Assert(err, IsNil)

	cases[util.PosToStr(coverage.Pos)] = map[string]int32{
		"file1": 1,
		"file2": 2,
		"total": 3,
	}

	pattern.Pos.Pos.Line = 138
	err = store.WriteLogPattern(context.Background(), pattern)
	c.Assert(err, IsNil)

	coverage.CovCountByLog["file3"] = 4
	coverage.CovCountByLog["file4"] = 4
	coverage.CovCount = 11
	err = store.WriteLogCoverage(context.Background(), coverage)
	c.Assert(err, IsNil)

	// one file only merged once
	cases[util.PosToStr(coverage.Pos)] = map[string]int32{
		"file1": 1,
		"file2": 2,
		"file3": 3,
		"file4": 4,
		"total": 10,
	}

	pattern.Pos.Pos.Line = 178
	unknownLog := &logpattern_go_proto.UnknowLogPattern{
		Pos:      pattern.Pos,
		CovCount: 7,
		CovCountByLog: map[string]int32{
			"file1": 1,
			"file2": 3,
			"file3": 3,
		},
		Level:   "error",
		Message: "test",
	}
	err = store.WriteUnknownLog(context.Background(), unknownLog)
	c.Assert(err, IsNil)
	cases[util.PosToStr(unknownLog.Pos)] = map[string]int32{
		"file1": 1,
		"file2": 2,
		"file3": 3,
		"total": 6,
	}

	return cases
}

func (t *testMergeSuite) testMockStoreA(c *C, store *storage.LogPatternStore) map[string]map[string]int32 {
	cases := make(map[string]map[string]int32)

	pattern := &logpattern_go_proto.LogPattern{
		Pos: &logpattern_go_proto.Position{
			PackageInfo: &logpattern_go_proto.Package{
				Repo: "github.com/pingcap/ticdc/dm",
			},
			FilePath: "dm/dm/master3/config.go",
			Pos: &logpattern_go_proto.CodePosition{
				Line:   154,
				Column: 7274,
			},
		},
		Func: &logpattern_go_proto.FuncInfo{
			Name: "Toml",
			Pos: &logpattern_go_proto.Position{
				FilePath: "dm/dm/master/config.go",
				Pos: &logpattern_go_proto.CodePosition{
					Line:   138,
					Column: 7140,
				},
			},
		},
		Level:     "error",
		Signature: []string{"\"fail to marshal config to % wwwww"},
	}

	err := store.WriteLogPattern(context.Background(), pattern)
	c.Assert(err, IsNil)

	coverage := &logpattern_go_proto.Coverage{
		Pos:      pattern.Pos,
		CovCount: 3,
		CovCountByLog: map[string]int32{
			"file1": 1,
			"file2": 2,
		},
	}
	err = store.WriteLogCoverage(context.Background(), coverage)
	c.Assert(err, IsNil)

	cases[util.PosToStr(coverage.Pos)] = map[string]int32{
		"file1": 1,
		"file2": 2,
		"total": 3,
	}

	pattern.Pos.Pos.Line = 138
	err = store.WriteLogPattern(context.Background(), pattern)
	c.Assert(err, IsNil)

	coverage.CovCountByLog["file3"] = 3
	coverage.CovCount = 6
	err = store.WriteLogCoverage(context.Background(), coverage)
	c.Assert(err, IsNil)

	cases[util.PosToStr(coverage.Pos)] = map[string]int32{
		"file1": 1,
		"file2": 2,
		"file3": 3,
		"total": 6,
	}

	pattern.Pos.Pos.Line = 178
	unknownLog := &logpattern_go_proto.UnknowLogPattern{
		Pos:      pattern.Pos,
		CovCount: 3,
		CovCountByLog: map[string]int32{
			"file1": 1,
			"file2": 2,
		},
		Level:   "error",
		Message: "test",
	}
	err = store.WriteUnknownLog(context.Background(), unknownLog)
	c.Assert(err, IsNil)
	cases[util.PosToStr(unknownLog.Pos)] = map[string]int32{
		"file1": 1,
		"file2": 2,
		"total": 3,
	}

	return cases
}
