package write

import (
	"bytes"
	"encoding/json"
	"escalation/prom/model"
	"os"
	"reflect"
	"sort"
	"strconv"
	"strings"

	// "io"
	"log"
	"net/http"
	"net/url"
	"time"

	. "escalation/prom/util"

	"github.com/gogo/protobuf/proto"
	"github.com/golang/snappy"
	. "github.com/prometheus/prometheus/prompb"
)

func ConstJsonStringMap(tagName string) string {
	jsonMap := make(map[string]string)
	jsonMap["MetricName"] = "__name__"
	jsonMap["Cluster"] = "cluster"
	jsonMap["ClusterId"] = "cluster_id"
	jsonMap["Component"] = "component"
	jsonMap["Instance"] = "instance"
	jsonMap["Job"] = "job"
	jsonMap["KubernetesNamespace"] = "kubernetes_namespace"
	jsonMap["Name"] = "name"
	jsonMap["Project"] = "project"
	jsonMap["Receive"] = "receive"
	jsonMap["SeedProvider"] = "seed_provider"
	jsonMap["SeedRegion"] = "seed_region"
	jsonMap["ShootInfra"] = "shoot_infra"
	jsonMap["ShootName"] = "shoot_name"
	jsonMap["Tenant"] = "tenant"
	jsonMap["TenantId"] = "tenant_id"
	jsonMap["TidbCluster"] = "tidb_cluster"
	jsonMap["Type"] = "type"
	jsonMap["Db"] = "db"
	return jsonMap[tagName]
}

func toPromTIme(origin string) string {
	split_slice := strings.Split(origin, " ")
	return split_slice[0] + "T" + split_slice[1] + ".000Z"
}

const REMOTE_PROM_URL = `http://promethues-enpoint-a.observability.tidbcloud.com/047f9bb7-ebde-411c-86a3-dae713ee5445/api/v1/write`
const LOCAL_PROM_URL = `http://0.0.0.0:9090/api/v1/write`

func GenerateRequest(ts []TimeSeries, mm []MetricMetadata) *WriteRequest {
	return &WriteRequest{Timeseries: ts, Metadata: mm}
}

func TestWriteRequest() *WriteRequest {
	wr := &WriteRequest{
		Timeseries: []TimeSeries{
			{
				Labels: []Label{
					{
						Name:  "__name__",
						Value: "metric1",
					},
				},
				Samples: []Sample{
					{
						Timestamp: 1667357022003,
						// Timestamp: int64(time.Now().Unix()),
						Value: 101.0,
					},
				},
			},
		},
	}
	return wr
}

func SendWriteRequest(wr *WriteRequest) {
	data, err := proto.Marshal(wr)
	if err != nil {
		panic(err)
	}
	// Encode the content into snappy encoding.
	encoded := snappy.Encode(nil, data)
	body := bytes.NewReader(encoded)
	// Create an HTTP request from the body content and set necessary parameters.
	req, err := http.NewRequest("POST", REMOTE_PROM_URL, body)
	if err != nil {
		panic(err)
	}
	// req.Close = true
	// Set the required HTTP header content.
	req.Header.Set("Content-Type", "application/x-protobuf")
	req.Header.Set("Content-Encoding", "snappy")
	req.Header.Set("X-Prometheus-Remote-Write-Version", "0.1.0")

	res, err1 := GetHttpClient().Do(req)
	if err1 != nil {
		log.Println("error of sending request: ", err1)
		SetHttpClient()
		GetHttpClient().Do(req)
		return
	}
	if res.StatusCode != 204 {
		log.Println("res of sending request: ", res)
	}

}

func FetchAndWrite(metrics []string, startTime string, endTime string, clusters []model.ClusterInf) {
	loc, _ := time.LoadLocation("UTC")
	create_time, err_created := time.ParseInLocation("2006-01-02 15:04:05", startTime, loc)
	if err_created != nil {
		panic(err_created)
	}
	finish_time, err_finish := time.ParseInLocation("2006-01-02 15:04:05", endTime, loc)
	if err_finish != nil {
		panic(err_finish)
	}
	start_ts := create_time.Unix()
	for start_ts+899 < finish_time.Unix() {
		var end_ts int64
		if start_ts+899 < finish_time.Unix() {
			end_ts = start_ts + 899 // add 15 min
		} else {
			end_ts = finish_time.Unix()
		}
		// write 15m's metrics of all clusters.
		wrs := make([]*WriteRequest, 0)
		log.Println("start time: ", time.Unix(start_ts, 0).UTC().Format("2006-01-02 15:04:05"))
		log.Println("end time: ", time.Unix(end_ts, 0).UTC().Format("2006-01-02 15:04:05"))
		for _, cluster := range clusters {
			clusterURL := cluster.PromURL
			for _, metric := range metrics {
				oneClusterRequest := GetOneMetricRequest(metric, clusterURL, toPromTIme(time.Unix(start_ts, 0).UTC().Format("2006-01-02 15:04:05")), toPromTIme(time.Unix(end_ts, 0).UTC().Format("2006-01-02 15:04:05")))
				wrs = append(wrs, oneClusterRequest...)
			}
		}
		sort.Slice(wrs, func(i, j int) bool {
			return wrs[i].Timeseries[0].Samples[0].Timestamp < wrs[j].Timeseries[0].Samples[0].Timestamp
		})
		log.Println("total number of entities is:, ", len(wrs))
		WriteBatchToProm(wrs)
		start_ts += 900
		// Persist the newest cluster information to disk.
		jsonBytes, _ := json.Marshal(model.CompletedTime{Timestamp: start_ts})
		time_file, _ := os.Create("./persist/start_time.json")
		defer time_file.Close()
		time_file.Write(jsonBytes)
		log.Println("write success!")
	}
}

func GetOneMetricRequest(metric string, clusterURL string, start string, end string) []*WriteRequest {
	params := url.Values{}
	params.Add(`query`, metric)
	params.Add(`start`, start)
	params.Add(`end`, end)
	params.Add(`step`, `15m`)

	req, _ := http.NewRequest("GET", clusterURL+`/api/v1/query_range?`+params.Encode(), nil)
	log.Println("Get metrics via URL: " + clusterURL)
	var struct_res model.ThanosResponse
	SendAndHandleHTTPRes(req, &struct_res)
	log.Println("response: ", len(struct_res.Data.Result))

	wrs := BuildWriteRequest(struct_res.Data.Result, start)
	return wrs
}

func BuildWriteRequest(resArr []model.ThanosResult, time string) []*WriteRequest {
	wrs := make([]*WriteRequest, 0)
	for _, res := range resArr {
		labels := make([]Label, 0)
		t := reflect.TypeOf(res.Metric)
		v := reflect.ValueOf(res.Metric)
		for k := 0; k < t.NumField(); k++ {
			if v.Field(k).String() != "" {
				labels = append(labels, Label{Name: ConstJsonStringMap(t.Field(k).Name), Value: v.Field(k).String()})
			}
		}
		for _, value := range res.Values {
			tss := make([]TimeSeries, 0)
			samples := make([]Sample, 0)
			ts := value[0].(float64)
			ts_int64 := int64(ts * 1000)
			val := value[1].(string)
			val_float64, _ := strconv.ParseFloat(val, 64)
			samples = append(samples, Sample{Timestamp: ts_int64, Value: val_float64})
			tss = append(tss, TimeSeries{Labels: labels, Samples: samples})
			wr := &WriteRequest{
				Timeseries: tss,
			}
			wrs = append(wrs, wr)
		}

	}
	return wrs
}

func WriteBatchToProm(wrs []*WriteRequest) {
	for _, wr := range wrs {
		SendWriteRequest(wr)
	}
}
