package slowquery

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"sort"
	"strings"
)

type analyzeMetric struct {
	Metric      analyzeMetricTags `json:"metric"`
	TimestampMs []uint64          `json:"timestamps"` // ms
	Values      []uint64          `json:"values"`
}

type analyzeMetricTags struct {
	Name       string `json:"__name__"`
	Cluster    string `json:"cluster"`
	Digest     string `json:"digest"`
	Query      string `json:"query"`
	User       string `json:"user"`
	Database   string `json:"database"`
	Instance   string `json:"instance"`
	UseTiFlash string `json:"use_tiflash"`
}

type analyzeTsValue struct {
	ts    uint64
	value uint64
}

type analyzeTsValues []analyzeTsValue

func (a analyzeTsValues) Len() int           { return len(a) }
func (a analyzeTsValues) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a analyzeTsValues) Less(i, j int) bool { return a[i].ts < a[j].ts }

func analyze(env, cluster string, sls []*SlowLog) error {
	mapQueryTime := map[analyzeMetricTags]analyzeTsValues{}
	mapMemoryMax := map[analyzeMetricTags]analyzeTsValues{}
	for _, sl := range sls {
		tags := analyzeMetricTags{
			Name:     "slow_query_query_time",
			Cluster:  cluster,
			Digest:   sl.Digest,
			Query:    sl.Query,
			User:     sl.User,
			Database: sl.DB,
			Instance: sl.Instance,
		}
		if sl.Plan == "" {
			tags.UseTiFlash = "unknown"
		} else {
			sl.Plan = parsePlan(context.Background(), sl.Plan, sl.BinaryPlan)
			if strings.Contains(sl.Plan, "cop[tiflash]") {
				tags.UseTiFlash = "yes"
			} else {
				tags.UseTiFlash = "no"
			}
		}
		mapQueryTime[tags] = append(mapQueryTime[tags], analyzeTsValue{
			ts:    uint64(sl.Time.UnixMilli()),
			value: uint64(sl.QueryTime),
		})
		tags.Name = "slow_query_memory_max"
		mapMemoryMax[tags] = append(mapMemoryMax[tags], analyzeTsValue{
			ts:    uint64(sl.Time.UnixMilli()),
			value: sl.MemMax,
		})
	}
	if err := analyzeSave(env, cluster, mapQueryTime); err != nil {
		return err
	}
	if err := analyzeSave(env, cluster, mapMemoryMax); err != nil {
		return err
	}
	return nil
}

func analyzeSave(env, cluster string, data map[analyzeMetricTags]analyzeTsValues) error {
	metrics := make([]analyzeMetric, 0, len(data))
	for tags, tsvs := range data {
		sort.Sort(tsvs)
		timestamps := make([]uint64, 0, len(tsvs))
		values := make([]uint64, 0, len(tsvs))
		for _, tsv := range tsvs {
			timestamps = append(timestamps, tsv.ts)
			values = append(values, tsv.value)
		}
		metrics = append(metrics, analyzeMetric{
			Metric:      tags,
			TimestampMs: timestamps,
			Values:      values,
		})
	}
	buf, err := analyzeEncode(metrics)
	if err != nil {
		return err
	}
	resp, err := http.Post(analyzeVmImportAddr(env, cluster), "application/json", buf)
	if err != nil {
		return err
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("failed to call vm import, code=%d", resp.StatusCode)
	}
	return nil
}

func analyzeEncode(metrics []analyzeMetric) (*bytes.Buffer, error) {
	buf := bytes.NewBuffer(nil)
	enc := json.NewEncoder(buf)
	for _, m := range metrics {
		if err := enc.Encode(m); err != nil {
			return nil, err
		}
	}
	return buf, nil
}

func analyzeVmImportAddr(env, cluster string) string {
	path := fmt.Sprintf("/clinic-vminsert/insert/%v/prometheus/api/v1/import", cluster)
	switch strings.ToLower(env) {
	case "staging":
		return "http://promethues-enpoint-a.observability-staging.pingcap.cloud" + path
	case "prod":
		return "http://promethues-enpoint-a.observability.tidbcloud.com" + path
	case "dev":
		fallthrough
	default:
		return "http://promethues-enpoint-01.shared.aws.tidbcloud.com" + path
	}
}
