package points

import (
	"encoding/json"
	"git.quantgroup.cn/DevOps/enoch/pkg/glog"
	"github.com/influxdata/influxdb/client/v2"
	"time"
)

type ChunkMsg struct {
	AppName   string     `json:"appName"`
	Ip        string     `json:"ip"`
	EndPoints []EndPoint `json:"endPoints"`
}

type EndPoint struct {
	Health    Health      `json:"health"`
	Metrics   MetricsInfo `json:"metrics"`
	Timestamp int64       `json:"timestamp"`
}
type Health struct {
	Status  Status `json:"status"`
	Details Detail `json:"details"`
}

type Detail struct {
	DiskSpace DiskInfo    `json:"diskSpace"`
	Redis     RedisInfo   `json:"redis"`
	Db        interface{} `json:"db"`
}

type DBInfo struct {
	Details map[string]DBDetail `json:"details"`
	Status  Status              `json:"status"`
}

type DBDetail struct {
	Status  Status       `json:"status"`
	Details DBDetailInfo `json:"details"`
}

type DBDetailInfo struct {
	Database string `json:"database"`
}

/**
硬盘信息
*/
type DiskInfo struct {
	Details DiskDetail `json:"details"`
	Status  Status     `json:"status"`
}

/**
硬盘详情
*/
type DiskDetail struct {
	Total     int64 `json:"total"`
	Free      int64 `json:"free"`
	Threshold int64 `json:"threshold"`
}

/**
redis
*/
type RedisInfo struct {
	Status Status `json:"status"`
}

type Status struct {
	Code        string `json:"code"`
	Description string `json:"description"`
}

type MetricsInfo struct {
	Mem                        int     `json:"mem"`
	MemFree                    int     `json:"mem.free"`
	Processors                 int     `json:"processors"`
	InstanceUptime             int     `json:"instance.uptime"`
	Uptime                     int     `json:"uptime"`
	SystemloadAverage          float64 `json:"systemload.average"`
	HeapCommitted              int     `json:"heap.committed"`
	HeapInit                   int     `json:"heap.init"`
	HeapUsed                   int     `json:"heap.used"`
	Heap                       int     `json:"heap"`
	NonheapCommitted           int     `json:"nonheap.committed"`
	NonheapInit                int     `json:"nonheap.init"`
	NonheapUsed                int     `json:"nonheap.used"`
	Nonheap                    int     `json:"nonheap"`
	ThreadsPeak                int     `json:"threads.peak"`
	ThreadsDaemon              int     `json:"threads.daemon"`
	ThreadsTotalStarted        int     `json:"threads.totalStarted"`
	Threads                    int     `json:"threads"`
	Classes                    int     `json:"classes"`
	ClassesLoaded              int     `json:"classes.loaded"`
	ClassesUnloaded            int     `json:"classes.unloaded"`
	GcParnewCount              int     `json:"gc.parnew.count"`
	GcParnewTime               int     `json:"gc.parnew.time"`
	GcConcurrentmarksweepCount int     `json:"gc.concurrentmarksweep.count"`
	GcConcurrentmarksweepTime  int     `json:"gc.concurrentmarksweep.time"`
}

func HostBaseInfoToPoint(data []byte) ([]*client.Point, error) {
	rtn := make([]*client.Point, 0)

	chunkMsg := ChunkMsg{}
	err := json.Unmarshal(data, &chunkMsg)
	if err != nil {
		glog.Error("can not unmarshal json:", data)
		return nil, err
	}

	for _, healthInfo := range chunkMsg.EndPoints {
		unixTime := time.Unix(0, healthInfo.Timestamp*1000000)
		metricsPoint := buildMetricsInfluxMsg(chunkMsg.AppName, chunkMsg.Ip, unixTime, healthInfo.Health, healthInfo.Metrics)
		if metricsPoint != nil {
			rtn = append(rtn, metricsPoint)
		}
		dbByte, _ := json.Marshal(healthInfo.Health.Details.Db)
		healthPoint := buildHealthInfluxMsg(chunkMsg.AppName, chunkMsg.Ip, unixTime, &dbByte)
		if healthPoint != nil {
			rtn = append(rtn, healthPoint)
		}
	}

	return rtn, nil
}

func buildHealthInfluxMsg(appName string, ip string, timestamp time.Time, db *[]byte) *client.Point {
	tags := make(map[string]string)
	tags["sys_name"] = appName
	tags["host"] = ip

	fields := make(map[string]interface{})
	dbInfo := DBInfo{}
	err := json.Unmarshal(*db, &dbInfo)
	if err != nil {
		dbDetails := DBDetail{}
		if err := json.Unmarshal(*db, &dbDetails); err != nil {
			return nil
		} else {
			fields[dbDetails.Details.Database] = isOK(dbDetails.Status.Code)
		}
	} else {
		for k, v := range dbInfo.Details {
			fieldName := v.Details.Database + "—" + k
			fields[fieldName] = isOK(v.Status.Code)
		}
	}

	if point, err := client.NewPoint("health_info", tags, fields, timestamp); err != nil {
		return nil
	} else {
		return point
	}
}

func buildMetricsInfluxMsg(appName string, ip string, timestamp time.Time, health Health, metrics MetricsInfo) *client.Point {
	tags := make(map[string]string)
	tags["sys_name"] = appName
	tags["host"] = ip

	fields := make(map[string]interface{})
	fields["sever_status"] = isOK(health.Status.Code)
	fields["redis_status"] = isOK(health.Details.Redis.Status.Code)
	diskSpace := health.Details.DiskSpace.Details
	fields["disk_tol"] = diskSpace.Total
	fields["disk_free"] = diskSpace.Free
	fields["disk_threshold"] = diskSpace.Threshold
	fields["mem_tol"] = metrics.Mem
	fields["mem_free"] = metrics.MemFree
	fields["heap_tol"] = metrics.Heap
	fields["heap_init"] = metrics.HeapInit
	fields["heap_used"] = metrics.HeapUsed
	fields["nonheap_tol"] = metrics.Nonheap
	fields["nonheap_init"] = metrics.NonheapInit
	fields["nonheap_used"] = metrics.NonheapUsed
	fields["nonheap_commit"] = metrics.NonheapCommitted
	fields["thread_tol"] = metrics.ThreadsTotalStarted
	fields["thread_peak"] = metrics.ThreadsPeak
	fields["thread_daemon"] = metrics.ThreadsDaemon
	fields["class_tol"] = metrics.Classes
	fields["class_loaded"] = metrics.ClassesLoaded
	fields["class_unloaded"] = metrics.ClassesUnloaded
	fields["gc_parnew_count"] = metrics.GcParnewCount
	fields["gc_parnew_time"] = metrics.GcParnewTime
	fields["gc_concurrent_mark_sweep"] = metrics.GcConcurrentmarksweepCount
	fields["gc_concurrent_mark_time"] = metrics.GcConcurrentmarksweepTime
	fields["uptime"] = metrics.Uptime
	fields["instance_uptime"] = metrics.InstanceUptime
	fields["system_load_average"] = metrics.SystemloadAverage
	fields["processors"] = metrics.Processors

	if point, err := client.NewPoint("machine_info", tags, fields, timestamp); err != nil {
		return nil
	} else {
		return point
	}
}

func isOK(code string) int {
	if "UP" == code {
		return 1
	}
	return 0
}
