package sysbench

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
)

var (
	_start_line      = []byte(`sysbench --config-file=config --tables=`)
	_stat_start_line = []byte(`SQL statistics:`)
)

const (
	_start_line_fmt = `sysbench --config-file=config --tables=%d --table-size=%d --threads=%d %s run`
	stat_line_count = 25
	stat_info_fmt   = `    queries performed:
        read:                            %d
        write:                           %d
        other:                           %d
        total:                           %d
    transactions:                        %d (%f per sec.)
    queries:                             %d (%f per sec.)
    ignored errors:                      %d      (%f per sec.)
    reconnects:                          %d      (%f per sec.)

General statistics:
    total time:                          %fs
    total number of events:              %d

Latency (ms):
         min:                                    %f
         avg:                                    %f
         max:                                  %f
         95th percentile:                       %f
         sum:                            %f

Threads fairness:
    events (avg/stddev):           %f/%f
    execution time (avg/stddev):   %f/%f`
	report_fmt = `[ %ds ] thds: %d tps: %f qps: %f (r/w/o: %f/%f/%f) lat (ms,95%%): %f err/s: %f reconn/s: %f`
)

type statQueries struct {
	Read, Write, Other, Total               int
	Tran, Queries, Igerr, Recon             int
	Tran_avg, Que_avg, Igerr_avg, Recon_avg float64
}

type statGeneral struct {
	TimeCost float64
	Events   int
}

type statLatency struct {
	Min, Avg, Max, L95th, Sum float64
}

type statThread struct {
	Events_avg, Events_stddev float64
	Etime_avg, Etime_stddev   float64
}

type testInfo struct {
	Table, Table_size, Threads int
	Sys_type                   string
}

type statInfo struct {
	Time_str  string
	Test_info testInfo
	//
	Query   statQueries
	General statGeneral
	Latency statLatency
	Thread  statThread
	//
	Report []report
}

type report struct {
	Time, Thds                          int
	Tps, Qps, R, W, O, Lat95, Err, Reco float64
}

func gotoLine(reader *bufio.Reader, tag []byte) (line []byte, err error) {
	for {
		line, _, err = reader.ReadLine()
		if err != nil {
			return
		}
		if len(_start_line) > 0 && !bytes.Equal(tag, _start_line) {
			if bytes.Contains(line, _start_line) {
				return nil, fmt.Errorf("expect '%s' but not found before line '%s'", string(_start_line), string(line))
			}
		}
		if bytes.Contains(line, tag) {
			return
		}
	}
}

func parseTime(reader *bufio.Reader) (string, error) {
	line, _, err := reader.ReadLine()
	if err != nil {
		return "", err
	}
	idx := bytes.Index(line, []byte(" <"))
	start := idx + 2
	end := start + len("04-29 14:05:00")
	return string(line[start:end]), nil
}

func parseStartLine(reader *bufio.Reader) (ti testInfo, err error) {
	line, err := gotoLine(reader, _start_line)
	if err != nil {
		return ti, err
	}
	//
	idx := bytes.Index(line, []byte("sysbench"))
	if idx == -1 {
		fmt.Println("line=", string(line))
	}
	//
	_, err = fmt.Sscanf(string(line[idx:]), _start_line_fmt,
		&ti.Table, &ti.Table_size, &ti.Threads, &ti.Sys_type)
	return ti, err
}

//[ 600s ] thds: 1024 tps: 2626.88 qps: 52564.04 (r/w/o: 36798.37/10483.45/5282.22) lat (ms,95%): 569.67 err/s: 0.00 reconn/s: 0.00
func parseReportAndStat(reader *bufio.Reader) (ti []report, err error) {
	_, err1 := gotoLine(reader, []byte("Threads started!"))
	if err1 != nil {
		return ti, err1
	}
	line, _, err := reader.ReadLine()
	if len(bytes.TrimSpace(line)) == 0 {
		line, _, err = reader.ReadLine()
	}
	//base;
	for {
		if line[0] != '[' {
			break
		}
		r := report{}
		fmt.Sscanf(string(line), report_fmt,
			&r.Time, &r.Thds, &r.Tps, &r.Qps, &r.Lat95, &r.Err, &r.Reco)
		line, _, err = reader.ReadLine()
		ti = append(ti, r)
	}
	for i := 0; i < len(line); i++ {
		reader.UnreadByte()
	}
	return
}

func parseStat(reader *bufio.Reader, r *statInfo) (e error) {
	//contract input;
	_, e = gotoLine(reader, _stat_start_line)
	if e != nil {
		return fmt.Errorf("gotoLine err:%s", e.Error())
	}
	var sb strings.Builder
	for i := 0; i < stat_line_count; i++ {
		line, _, err := reader.ReadLine()
		if err != nil {
			fmt.Printf("read err:%s;line=%s.", err.Error(), string(line))
			return err
		}
		sb.Write(line)
		sb.WriteByte('\n')
	}
	//parse input;
	q := &r.Query
	la := &r.Latency
	_, e = fmt.Sscanf(sb.String(), stat_info_fmt,
		&q.Read, &q.Write, &q.Other, &q.Total,
		&q.Tran, &q.Tran_avg, &q.Queries, &q.Que_avg, &q.Igerr, &q.Igerr_avg, &q.Recon, &q.Recon_avg,
		&r.General.TimeCost, &r.General.Events,
		&la.Min, &la.Avg, &la.Max, &la.L95th, &la.Sum,
		&r.Thread.Events_avg, &r.Thread.Events_stddev, &r.Thread.Etime_avg, &r.Thread.Etime_avg)
	if e != nil {
		fmt.Println(sb.String())
	}
	return
}

var errFinish = fmt.Errorf("process finished")

func parseOneSysbench(reader *bufio.Reader, fname string) (si statInfo, err error) {
	ti, err := parseStartLine(reader)
	if err != nil {
		if err == io.EOF {
			return si, errFinish
		}
		return si, fmt.Errorf("parseStartLine(%s) err:%s", fname, err.Error())
	}
	si.Test_info = ti
	si.Time_str, err = parseTime(reader)
	if err != nil {
		return si, fmt.Errorf("parseTime(%s) err:%s", fname, err.Error())
	}

	err = parseStat(reader, &si)
	if err != nil {
		return si, fmt.Errorf("parseStat(%s) err:%s", fname, err.Error())
	}
	return si, nil
}

type SysbenchStat struct {
	Sis  []statInfo
	name string
}

func parseSysbenchFile(fname string) (*SysbenchStat, error) {
	fp, err := os.Open(fname)
	if err != nil {
		return nil, err
	}
	var ss SysbenchStat
	defer fp.Close()
	reader := bufio.NewReader(fp)
	//
	var sis []statInfo
	for {
		si, err := parseOneSysbench(reader, fname)
		if err != nil {
			if err == errFinish {
				break
			} else {
				return nil, err
			}
		}

		sis = append(sis, si)
	}
	ss.Sis = sis
	return &ss, nil
}

func parseSysbencDir(dir string) ([]*SysbenchStat, error) {
	var ret []*SysbenchStat
	err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
		fname := filepath.Base(path)
		idx := strings.Index(fname, ".log")
		if idx == -1 {
			return nil
		}
		o, err := parseSysbenchFile(path)
		if err != nil {
			return err
		}
		o.name = fname
		ret = append(ret, o)
		return nil
	})
	return ret, err
}

type statList struct {
	csvlins []string
}

func otherInfo(si statInfo) string {
	return fmt.Sprintf("thd:%d;tm:%s", si.Test_info.Threads, si.Time_str)
}

func hdrName(filename string) string {
	idx := strings.Index(filename, ".log")
	return filename[:idx]
}

func mtype(name string) string {
	if strings.Contains(name, "iokv") {
		return "io"
	}
	if strings.Contains(name, "cokv") {
		return "co"
	}
	return ""
}

var sortString = []string{"oltp_point_select", "oltp_read_write", "oltp_write_only", "oltp_update_index", "oltp_update_non_index"}

func NewStatList() *statList {
	sl := &statList{}
	sl.csvlins = make([]string, len(sortString))
	return sl
}

func reportTime(sec, base int) string {
	if base > 1800 {
		return fmt.Sprintf("%f'h", float64(sec)/float64(3600))
	}
	if base > 60 {
		return fmt.Sprintf("%f'm", float64(sec)/float64(60))
	}
	return fmt.Sprintf("%d's", sec)
}

func reportToCSV(rs []report, name string) {
	fp, err := os.Create(name)
	if err != nil {
		fmt.Printf("write %s error:%s.\n", name, err.Error())
		return
	}
	defer fp.Close()
	fmt.Fprintf(fp, "report-interval(0.5h),tps,qps,read,write,other,95th")
	for _, r := range rs {
		fmt.Fprintf(fp, "%s,%f,%f,%f,%f,%f,%f",
			reportTime(r.Time, rs[0].Time), r.Tps, r.Qps, r.R, r.W, r.O, r.Lat95)
	}
}

func toCSV(rets []*SysbenchStat, out string) error {
	var tomap = map[string]*statList{}
	title := `oltp,from,type,qps,tps,avg,95th,thread,start-time` + "\n"
	for _, ret := range rets {
		fname := hdrName(ret.name)
		mtyp := mtype(fname)
		for _, sis := range ret.Sis {

			stype := sis.Test_info.Sys_type
			sl, ok := tomap[stype]
			if !ok {
				sl = NewStatList()
				tomap[stype] = sl
			}
			q := sis.Query
			la := sis.Latency

			s := fmt.Sprintf("%s,%s,%s,%f,%f,%f,%f,%d,%s\n",
				stype, fname, mtyp,
				q.Que_avg, q.Tran_avg, la.Avg, la.L95th,
				sis.Test_info.Threads, sis.Time_str)
			sl.csvlins = append(sl.csvlins, s)
		}
	}
	//write;
	fp, err := os.Create(out)
	if err != nil {
		return err
	}
	fp.WriteString(title)
	for _, oltp := range sortString {
		v, ok := tomap[oltp]
		if ok {
			for _, ls := range v.csvlins {
				fp.WriteString(ls)
			}
			fp.WriteString("\n")
			delete(tomap, oltp)
		}
	}
	for _, v := range tomap {
		for _, ls := range v.csvlins {
			fp.WriteString(ls)
		}
		fp.WriteString("\n")
	}
	fp.Close()
	return nil
}
