package debug

import (
	"fmt"
	"path/filepath"
	"sort"

	logpattern_go_proto "github.com/pingcap-inc/alioth/proto"
)

var defaultUnknowsTemplate = `
unknown logs not captured by logcov extract
Total: {{.Total}}, occurrence: {{.Count}}
{{- println }}
{{- range .Details}}
{{.Path}} count {{.Count}}
log detail:
{{- range .Details}}
Position: {{.Position}}, Count: {{.Count}}
Message: {{.Message}}
{{- range $file, $count := .CountByFile}}
log from {{$file}}, count {{$count}}
{{- end}}
{{- end}}
{{end}}
`

var defaultCoverageTemplate = `
log coverage
Total: {{.Total}}, occurrence: {{.Count}}
{{- println }}
{{- range .Details}}
{{.Path}} count {{.Count}}
log detail:
{{- range .Details}}
Position: {{.ShortPos}}, Count: {{.Count}}
Message: {{.Message}}
{{- range $file, $count := .CountByFile}}
log from {{$file}}, count {{$count}}
{{- end}}
{{- end}}
{{end}}
`

var defaultPatternTemplate = `
log pattern
Total: {{.Total}}, Covered: {{.TotalCov}}, cover case occurrence {{.Count}}
{{- println }}
{{- range .Details}}
{{.Path}}, Count: {{.Count}}
log detail:
{{- range .Details}}
Position: {{.ShortPos}}, Message: {{.Message}}  {{if gt .Count 0 }} cov({{ .Count }}) {{- else}} {{- end}}
{{- end}}
{{end}}
`

// UnKnowLogToDetail converts UnknowLogPattern to LogDetail
func UnKnowLogToDetail(record *logpattern_go_proto.UnknowLogPattern) *LogDetail {
	Codefile := record.GetPos().FilePath
	CodePosition := fmt.Sprintf("%s:%d", Codefile, record.GetPos().Pos.Line)

	detail := &LogDetail{
		Position:    CodePosition,
		ShortPos:    CodePosition,
		Level:       record.Level,
		Message:     record.Message,
		Count:       int(record.CovCount),
		CountByFile: make(map[string]int),
	}

	for file, count := range record.CovCountByLog {
		detail.CountByFile[file] = int(count)
	}

	return detail
}

// CoverageToDetail converts record to LogDetail
func CoverageToDetail(record *logpattern_go_proto.Coverage) *LogDetail {
	Codefile := record.GetPos().FilePath
	CodePosition := fmt.Sprintf("%s:%d", Codefile, record.GetPos().Pos.Line)
	ShortCodePosition := fmt.Sprintf("%s:%d", filepath.Base(Codefile), record.GetPos().Pos.Line)

	detail := &LogDetail{
		Position:    CodePosition,
		ShortPos:    ShortCodePosition,
		Message:     record.Message,
		Count:       int(record.CovCount),
		CountByFile: make(map[string]int),
	}

	for file, count := range record.CovCountByLog {
		detail.CountByFile[file] = int(count)
	}

	return detail
}

// PatternToDetail converts record to LogDetail
func PatternToDetail(record *logpattern_go_proto.LogPattern, cov *logpattern_go_proto.Coverage) *LogDetail {
	Codefile := record.GetPos().FilePath
	CodePosition := fmt.Sprintf("%s:%d", Codefile, record.GetPos().Pos.Line)
	ShortCodePosition := fmt.Sprintf("%s:%d", filepath.Base(Codefile), record.GetPos().Pos.Line)

	message := ""
	if len(record.Signature) > 0 {
		message = record.Signature[0]
	}
	fn := ""
	if record.Func != nil {
		fn = record.Func.Name
	}

	detail := &LogDetail{
		Position: CodePosition,
		ShortPos: ShortCodePosition,
		Message:  message,
		Fun:      fn,
		Level:    record.Level,
	}

	if cov != nil {
		detail.Count = int(cov.CovCount)
	}

	return detail
}

type LogDetail struct {
	// log printing in which function, it may be empty
	Fun string
	// log printing level
	Level string
	// log printing position in the code file
	Position string
	// log printing short position in the code file
	ShortPos string
	// A log message example in the log file
	Message string
	// total number of occurrences in all log files
	Count int
	// number of occurrences in the each log file
	CountByFile map[string]int
}

func (l *LogDetail) String() string {
	if len(l.CountByFile) > 0 {
		return fmt.Sprintf("[position %s] [level %s] [function %s] [count %d] [message %s] [%+v]", l.Position, l.Level, l.Fun, l.Count, l.Message, l.CountByFile)
	}

	return fmt.Sprintf("[position %s] [level %s] [function %s] [count %d] [message %s]", l.Position, l.Level, l.Fun, l.Count, l.Message)
}

// used to sort logDetail by position
func (l *LogDetail) GetSortKey() string {
	return l.Position
}

// used to sort logDetail by position
func (l *LogDetail) GetCount() int {
	return l.Count
}

type FileDetail struct {
	Details []*LogDetail
	// LogDetail.Position: index in .Deatils
	index map[string]int
	// code file path
	Path string
	// total number of occurrences of all log in the code file
	Count int
}

func (f *FileDetail) String() string {
	f.SortByKey()

	str := fmt.Sprintf("file %s count %d\n", f.Path, f.Count)
	for _, l := range f.Details {
		str = fmt.Sprintf("%s%s\n", str, l.String())
	}

	return str
}

func NewFileDetail(path string) *FileDetail {
	return &FileDetail{
		Path:  path,
		index: make(map[string]int),
	}
}

// AddLog adds LogDetail
// the LogDetails belongs to same code file are placed togerther
func (f *FileDetail) AddLog(position string, l *LogDetail) bool {
	_, ok := f.index[position]
	if !ok {
		f.Details = append(f.Details, l)
		f.index[position] = len(f.Details) - 1
		f.Count = f.Count + l.Count
	}
	return !ok
}

// SortByKey sorts LogDetail by thier position
func (f *FileDetail) SortByKey() {
	items := make(SortKeyArry, 0, len(f.Details))
	for _, detail := range f.Details {
		items = append(items, detail)
	}
	sort.Sort(items)

	f.index = make(map[string]int)
	for index, item := range items {
		fd := item.(*LogDetail)
		f.Details[index] = fd
		f.index[fd.Position] = index
	}
}

// SortByCount sorts LogDetail by thier occurrences number
func (f *FileDetail) SortByCount() {
	items := make(SortCountArry, 0, len(f.Details))
	for _, detail := range f.Details {
		items = append(items, detail)
	}
	sort.Sort(items)

	f.index = make(map[string]int)
	for index, item := range items {
		fd := item.(*LogDetail)
		f.Details[index] = fd
		f.index[fd.Position] = index
	}
}

// return code file path
func (f *FileDetail) GetSortKey() string {
	return f.Path
}

// return total number of occurrences of all log in the code file
func (f *FileDetail) GetCount() int {
	return f.Count
}

type LogSet struct {
	Details []*FileDetail
	// FileDetail.Path: index in .Deatils
	index map[string]int

	Total int

	TotalCov int

	Count int
}

func NewLogSet() *LogSet {
	return &LogSet{
		index: make(map[string]int),
	}
}

func (ls *LogSet) AddLog(file string, l *LogDetail) {
	index, ok := ls.index[file]
	if !ok {
		ls.Details = append(ls.Details, NewFileDetail(file))
		index = len(ls.Details) - 1
		ls.index[file] = index
	}

	if isNew := ls.Details[index].AddLog(l.Position, l); isNew {
		ls.Total = ls.Total + 1
	}

	if l.Count > 0 {
		ls.TotalCov += 1
	}
	ls.Count += l.Count
}

func (ls *LogSet) SortByKey() {
	items := make(SortKeyArry, 0, len(ls.Details))
	for _, detail := range ls.Details {
		items = append(items, detail)
	}
	sort.Sort(items)

	ls.index = make(map[string]int)
	for index, item := range items {
		fd := item.(*FileDetail)
		fd.SortByKey()
		ls.Details[index] = fd
		ls.index[fd.Path] = index
	}
}

func (ls *LogSet) SortByCount() {
	items := make(SortCountArry, 0, len(ls.Details))
	for _, detail := range ls.Details {
		items = append(items, detail)
	}
	sort.Sort(items)

	ls.index = make(map[string]int)
	for index, item := range items {
		fd := item.(*FileDetail)
		fd.SortByKey()
		ls.Details[index] = fd
		ls.index[fd.Path] = index
	}
}

type SortItem interface {
	// used key to sort
	GetSortKey() string
	// used count to sort
	GetCount() int
}

// SortArry implement sort.Interface to sort SortItems
type SortKeyArry []SortItem

func (s SortKeyArry) Len() int           { return len(s) }
func (s SortKeyArry) Less(i, j int) bool { return s[i].GetSortKey() < s[j].GetSortKey() }
func (s SortKeyArry) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// SortArry implement sort.Interface to sort SortItems
type SortCountArry []SortItem

func (s SortCountArry) Len() int           { return len(s) }
func (s SortCountArry) Less(i, j int) bool { return s[i].GetCount() < s[j].GetCount() }
func (s SortCountArry) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
