package producer

import (
	"github.com/mackerelio/go-osstat/memory"
	"github.com/mackerelio/go-osstat/loadavg"
	"github.com/mackerelio/go-osstat/network"
	"github.com/mackerelio/go-osstat/uptime"
	"github.com/mackerelio/go-osstat/cpu"
	"github.com/hako/durafmt"

	"amuz.es/src/infra/goutils/misc"
	"time"
	"go.uber.org/multierr"
	"sync"
	"runtime"
)

type NetIOInfo struct {
	Name    string `json:"name"`
	RxBytes uint64 `json:"rx_bytes"`
	TxBytes uint64 `json:"tx_bytes"`
}

type MemoryInfo struct {
	Total     uint64 `json:"total_bytes"`
	Active    uint64 `json:"active_bytes"`
	Cached    uint64 `json:"cached_bytes"`
	Free      uint64 `json:"free_bytes"`
	Inactive  uint64 `json:"inactive_bytes"`
	SwapFree  uint64 `json:"swap_free_bytes"`
	SwapTotal uint64 `json:"swap_total_bytes"`
	SwapUsed  uint64 `json:"swap_used_bytes"`
	Used      uint64 `json:"used_bytes"`
}

type LoadInfo struct {
	Avg1  float64 `json:"avg_1"`
	Avg5  float64 `json:"avg_5"`
	Avg15 float64 `json:"avg_15"`
}
type CPUInfo struct {
	Idle   float64 `json:"idle"`
	Nice   float64 `json:"nice"`
	System float64 `json:"system"`
	User   float64 `json:"user"`
}
type OSMetricInfo struct {
	Memory MemoryInfo           `json:"memory"`
	Load   LoadInfo             `json:"load"`
	NetIO  map[string]NetIOInfo `json:"net_io"`

	Uptime time.Duration `json:"uptime"`
	CPU    CPUInfo       `json:"cpu"`

	Host string    `json:"host"`
	At   time.Time `json:"at"`
}

func (p *OSMetricInfo) MarshalUptime() (name string, tags []map[string]string, fields []map[string]interface{}) {
	name, tags, fields =
		"uptime",
		[]map[string]string{
			{
				"os": runtime.GOOS,
			},
		},
		[]map[string]interface{}{
			{
				"duration": p.Uptime,
			},
		}
	return
}
func (p *OSMetricInfo) MarshalLoad() (name string, tags []map[string]string, fields []map[string]interface{}) {
	name, tags, fields =
		"load",
		[]map[string]string{
			{
				"os": runtime.GOOS,
			},
		},
		[]map[string]interface{}{
			{
				"avg_1":  p.Load.Avg1,
				"avg_5":  p.Load.Avg5,
				"avg_15": p.Load.Avg15,
			},
		}
	return
}
func (p *OSMetricInfo) MarshalCPU() (name string, tags []map[string]string, fields []map[string]interface{}) {
	name, tags, fields =
		"cpu",
		[]map[string]string{
			{
				"os": runtime.GOOS,
			},
		},
		[]map[string]interface{}{
			{
				"idle":   p.CPU.Idle,
				"nice":   p.CPU.Nice,
				"system": p.CPU.System,
				"user":   p.CPU.User,
			},
		}
	return
}

func (p *OSMetricInfo) MarshalNetworks() (name string, tags []map[string]string, fields []map[string]interface{}) {
	name = "network"
	tags = make([]map[string]string, 0)
	fields = make([]map[string]interface{}, 0)
	for name, info := range p.NetIO {
		tags, fields =
			append(tags, map[string]string{
				"os":   runtime.GOOS,
				"name": name,
			}),
			append(fields, map[string]interface{}{
				"rx_bytes": info.RxBytes,
				"tx_bytes": info.TxBytes,
			})
	}
	return
}

func (p *OSMetricInfo) MarshalMemory() (name string, tags []map[string]string, fields []map[string]interface{}) {
	name, tags, fields =
		"memory",
		[]map[string]string{
			{
				"os": runtime.GOOS,
			},
		},
		[]map[string]interface{}{
			{
				"total_bytes":      p.Memory.Total,
				"active_bytes":     p.Memory.Active,
				"cached_bytes":     p.Memory.Cached,
				"free_bytes":       p.Memory.Free,
				"inactive_bytes":   p.Memory.Inactive,
				"swap_free_bytes":  p.Memory.SwapFree,
				"swap_total_bytes": p.Memory.SwapTotal,
				"swap_used_bytes":  p.Memory.SwapUsed,
				"used_bytes":       p.Memory.Used,
			},
		}
	return
}
func (p *OSMetricInfo) Applier() (generator []OSMetricPointGen) {
	generator = append(
		generator,
		p.MarshalLoad,
		p.MarshalCPU,
		p.MarshalMemory,
		p.MarshalNetworks,
		p.MarshalUptime,
	)
	return
}

func (p *osMetric) readMemoryStat(info *OSMetricInfo, errChan chan<- error, waiter *sync.WaitGroup) {
	memoryInfo, err := memory.Get()

	defer func() {
		defer waiter.Done()
		if panicErr := recover(); panicErr != nil {
			err = multierr.Append(err, panicErr.(error))
		}
		if err != nil {
			p.logger.Error("unable to retrieve readMemoryStat: ", err)
			errChan <- err
		}
	}()

	if err != nil {
		return
	}
	p.logger.Debugf("memory total: ", misc.FileSizeIEC(memoryInfo.Total))
	p.logger.Debugf("memory active: ", misc.FileSizeIEC(memoryInfo.Active))
	p.logger.Debugf("memory cached: ", misc.FileSizeIEC(memoryInfo.Cached))
	p.logger.Debugf("memory free: ", misc.FileSizeIEC(memoryInfo.Free))
	p.logger.Debugf("memory inactive: ", misc.FileSizeIEC(memoryInfo.Inactive))
	p.logger.Debugf("memory swapFree: ", misc.FileSizeIEC(memoryInfo.SwapFree))
	p.logger.Debugf("memory swapTotal: ", misc.FileSizeIEC(memoryInfo.SwapTotal))
	p.logger.Debugf("memory swapUsed: ", misc.FileSizeIEC(memoryInfo.SwapUsed))
	p.logger.Debugf("memory used: ", misc.FileSizeIEC(memoryInfo.Used))

	info.Memory.Total = memoryInfo.Total
	info.Memory.Active = memoryInfo.Active
	info.Memory.Cached = memoryInfo.Cached
	info.Memory.Free = memoryInfo.Free
	info.Memory.Inactive = memoryInfo.Inactive
	info.Memory.SwapFree = memoryInfo.SwapFree
	info.Memory.SwapTotal = memoryInfo.SwapTotal
	info.Memory.SwapUsed = memoryInfo.SwapUsed
	info.Memory.Used = memoryInfo.Used
	return
}

func (p *osMetric) readLoadStat(info *OSMetricInfo, errChan chan<- error, waiter *sync.WaitGroup) {
	load, err := loadavg.Get()

	defer func() {
		defer waiter.Done()
		if panicErr := recover(); panicErr != nil {
			err = multierr.Append(err, panicErr.(error))
		}
		if err != nil {
			p.logger.Error("unable to retrieve readLoadStat: ", err)
			errChan <- err
		}
	}()

	if err != nil {
		return
	}
	p.logger.Debugf("load Loadavg1: %f \n", load.Loadavg1)
	p.logger.Debugf("load Loadavg5: %f \n", load.Loadavg5)
	p.logger.Debugf("load Loadavg15: %f \n", load.Loadavg15)

	info.Load.Avg1 = load.Loadavg1
	info.Load.Avg5 = load.Loadavg5
	info.Load.Avg15 = load.Loadavg15
	return
}

func (p *osMetric) readNetworkStat(info *OSMetricInfo, errChan chan<- error, waiter *sync.WaitGroup) {
	netios, err := network.Get()

	defer func() {
		defer waiter.Done()
		if panicErr := recover(); panicErr != nil {
			err = multierr.Append(err, panicErr.(error))
		}
		if err != nil {
			p.logger.Error("unable to retrieve readNetworkStat: ", err)
			errChan <- err
		}
	}()

	if err != nil {
		return
	}
	netIoMap := make(map[string]NetIOInfo)
	for _, netio := range netios {
		p.logger.Debugf("netio name: ", netio.Name)
		p.logger.Debugf("netio rxBytes: ", misc.FileSizeIEC(netio.RxBytes))
		p.logger.Debugf("netio txBytes: ", misc.FileSizeIEC(netio.TxBytes))
		netIoMap[netio.Name] = NetIOInfo{
			Name:    netio.Name,
			RxBytes: netio.RxBytes,
			TxBytes: netio.TxBytes,
		}
	}
	info.NetIO = netIoMap
	return
}

func (p *osMetric) readUptimeStat(info *OSMetricInfo, errChan chan<- error, waiter *sync.WaitGroup) {
	ut, err := uptime.Get()

	defer func() {
		defer waiter.Done()
		if panicErr := recover(); panicErr != nil {
			err = multierr.Append(err, panicErr.(error))
		}
		if err != nil {
			p.logger.Error("unable to retrieve readUptimeStat: ", err)
			errChan <- err
		}
	}()

	if err != nil {
		return
	}
	p.logger.Debugf("readUptimeStat: %s\n", durafmt.Parse(ut).String())

	info.Uptime = ut
	return
}

func (p *osMetric) readCpuStat(info *OSMetricInfo, errChan chan<- error, waiter *sync.WaitGroup) {
	ct, err := cpu.Get()

	defer func() {
		defer waiter.Done()
		if panicErr := recover(); panicErr != nil {
			err = multierr.Append(err, panicErr.(error))
		}
		if err != nil {
			p.logger.Error("unable to retrieve readCpuStat: ", err)
			errChan <- err
		}
	}()

	if err != nil {
		return
	}
	p.logger.Debugf("readCpuStat: idle=%d\n", ct.Idle)
	p.logger.Debugf("readCpuStat: nice=%d\n", ct.Nice)
	p.logger.Debugf("readCpuStat: system=%d\n", ct.System)
	p.logger.Debugf("readCpuStat: total=%d\n", ct.Total)
	p.logger.Debugf("readCpuStat: user=%d\n", ct.User)
	p.logger.Debugf("readCpuStat: idle=%f%%\n", float64(ct.Idle*100)/float64(ct.Total))
	p.logger.Debugf("readCpuStat: nice=%f%%\n", float64(ct.Nice*100)/float64(ct.Total))
	p.logger.Debugf("readCpuStat: system=%f%%\n", float64(ct.System*100)/float64(ct.Total))
	p.logger.Debugf("readCpuStat: user=%f%%\n", float64(ct.User*100)/float64(ct.Total))

	info.CPU.Idle = float64(ct.Idle*100) / float64(ct.Total)
	info.CPU.Nice = float64(ct.Nice*100) / float64(ct.Total)
	info.CPU.System = float64(ct.System*100) / float64(ct.Total)
	info.CPU.User = float64(ct.User*100) / float64(ct.Total)
	return
}

func (p *osMetric) availableMetrics() (appliers []osMetricApplier) {
	appliers = append(appliers,
		p.readLoadStat,
		p.readMemoryStat,
		p.readCpuStat,
		p.readNetworkStat,
		p.readUptimeStat,
	)
	return
}
