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"`
	RxKBytes int64  `json:"rx_kbytes"`
	TxKBytes int64  `json:"tx_kbytes"`
}

type MemoryInfo struct {
	Total     int64 `json:"total_kbytes"`
	Active    int64 `json:"active_kbytes"`
	Cached    int64 `json:"cached_kbytes"`
	Free      int64 `json:"free_kbytes"`
	Inactive  int64 `json:"inactive_kbytes"`
	SwapFree  int64 `json:"swap_free_kbytes"`
	SwapTotal int64 `json:"swap_total_kbytes"`
	SwapUsed  int64 `json:"swap_used_kbytes"`
	Used      int64 `json:"used_kbytes"`
}

type LoadInfo struct {
	Avg1  float64 `json:"avg_1"`
	Avg5  float64 `json:"avg_5"`
	Avg15 float64 `json:"avg_15"`
}
type CPUInfo struct {
	Total  float64 `json:"total"`
	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{}{
			{
				"total":   p.CPU.Total,
				"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_kbytes": info.RxKBytes,
				"tx_kbytes": info.TxKBytes,
			})
	}
	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_kbytes":      p.Memory.Total,
				"active_kbytes":     p.Memory.Active,
				"cached_kbytes":     p.Memory.Cached,
				"free_kbytes":       p.Memory.Free,
				"inactive_kbytes":   p.Memory.Inactive,
				"swap_free_kbytes":  p.Memory.SwapFree,
				"swap_total_kbytes": p.Memory.SwapTotal,
				"swap_used_kbytes":  p.Memory.SwapUsed,
				"used_kbytes":       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.Debug("memory total: ", misc.FileSizeIEC(memoryInfo.Total))
	p.logger.Debug("memory active: ", misc.FileSizeIEC(memoryInfo.Active))
	p.logger.Debug("memory cached: ", misc.FileSizeIEC(memoryInfo.Cached))
	p.logger.Debug("memory free: ", misc.FileSizeIEC(memoryInfo.Free))
	p.logger.Debug("memory inactive: ", misc.FileSizeIEC(memoryInfo.Inactive))
	p.logger.Debug("memory swapFree: ", misc.FileSizeIEC(memoryInfo.SwapFree))
	p.logger.Debug("memory swapTotal: ", misc.FileSizeIEC(memoryInfo.SwapTotal))
	p.logger.Debug("memory swapUsed: ", misc.FileSizeIEC(memoryInfo.SwapUsed))
	p.logger.Debug("memory used: ", misc.FileSizeIEC(memoryInfo.Used))

	info.Memory.Total = int64(memoryInfo.Total >> 10)
	info.Memory.Active = int64(memoryInfo.Active >> 10)
	info.Memory.Cached = int64(memoryInfo.Cached >> 10)
	info.Memory.Free = int64(memoryInfo.Free >> 10)
	info.Memory.Inactive = int64(memoryInfo.Inactive >> 10)
	info.Memory.SwapFree = int64(memoryInfo.SwapFree >> 10)
	info.Memory.SwapTotal = int64(memoryInfo.SwapTotal >> 10)
	info.Memory.SwapUsed = int64(memoryInfo.SwapUsed >> 10)
	info.Memory.Used = int64(memoryInfo.Used >> 10)
	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", load.Loadavg1)
	p.logger.Debugf("load Loadavg5: %f", load.Loadavg5)
	p.logger.Debugf("load Loadavg15: %f", 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.Debug("netio name: ", netio.Name)
		p.logger.Debug("netio rxBytes: ", misc.FileSizeIEC(netio.RxBytes))
		p.logger.Debug("netio txBytes: ", misc.FileSizeIEC(netio.TxBytes))
		netIoMap[netio.Name] = NetIOInfo{
			Name:     netio.Name,
			RxKBytes: int64(netio.RxBytes >> 10),
			TxKBytes: int64(netio.TxBytes >> 10),
		}
	}
	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", 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", ct.Idle)
	p.logger.Debugf("readCpuStat: nice=%d", ct.Nice)
	p.logger.Debugf("readCpuStat: system=%d", ct.System)
	p.logger.Debugf("readCpuStat: total=%d", ct.Total)
	p.logger.Debugf("readCpuStat: user=%d", ct.User)

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

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