package main

import (
	"bytes"
	"crypto/md5"
	"fmt"
	"io"
	"strings"

	"github.com/juju/errors"
	client "github.com/pingcap/schrodinger/pkg/test_client"
)

var (
	maxFuncCount = 5
)

// PanicStack is panic stack
type PanicStack struct {
	Func []string
	Line []string
	Raw  []byte
}

// Signature signs panic check
func (s *PanicStack) Signature() []byte {
	h := md5.New()
	for _, f := range s.Func {
		pos := strings.LastIndexByte(f, '(')
		if pos > 0 {
			io.WriteString(h, f[:pos])
		} else {
			io.WriteString(h, f)
		}
	}
	return h.Sum(nil)
}

func parsePanicData(text []byte, splitString string) ([]*PanicStack, error) {
	texts := splitPanicText(text, splitString)
	ret := make([]*PanicStack, 0, len(texts))
	for _, t := range texts {
		if strings.Contains(string(t), "Not implemented") || strings.Contains(string(t), "unsupported agg function") {
			client.Infof("panic: unsupported")
			continue
		}

		s, err := parsePanicStack(t)
		if err != nil && strings.Contains(err.Error(), "not found") {
			client.Infof("panic: not found")
			continue
		}

		if err != nil {
			return nil, errors.Trace(err)
		}
		ret = append(ret, s)
	}
	return ret, nil
}

func parsePanicStack(text []byte) (*PanicStack, error) {
	var stack PanicStack
	stringText := strings.ReplaceAll(string(text), "\\n", "\n")
	stringText = strings.ReplaceAll(stringText, "\\t", "\t")
	posHead := strings.Index(stringText, "panic.go") //Remove the head
	if posHead < 0 {
		return nil, errors.NotFoundf("panic.go")
	}
	posTail := strings.Index(stringText, "runtime.gopanic") //Remove the head
	if posTail < 0 {
		posTail = len(stringText)
	}
	goPanicStack := []byte(stringText)[posHead:posTail]
	lines := bytes.Split(goPanicStack, []byte("\n"))
	if len(lines) < 3 {
		return nil, errors.Errorf("wrong input:whole test is \n%s", text)
	}

	lines = lines[1:]
	lineCount := len(lines) / 2
	if lineCount > maxFuncCount {
		lineCount = maxFuncCount
	}
	lineIndex := 0
	funcLines := make([]string, 0, lineCount)
	for len(lines) >= 2 {
		if checkValidStackLin(lines[1]) {
			funcLines = append(funcLines, string(lines[0]))
			lines = lines[2:]
			lineIndex++
			if lineIndex > maxFuncCount {
				fmt.Println("break", lineIndex, maxFuncCount)
				break
			}
		} else {
			client.Warnf("function line:%s", string(lines[1]))
			break
		}
	}
	if len(funcLines) < 1 {
		return nil, errors.Errorf("wrong input:\n%s", text)
	}

	stack.Func = funcLines
	stack.Raw = []byte(stringText)[:posTail]
	return &stack, nil
}

func checkValidStackLin(line []byte) bool {
	for _, c := range line {
		if c != ' ' && c != '	' && c != '/' {
			return false
		} else if c == '/' {
			return true
		}
	}

	return false
}
