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/mackerelio/go-osstat/disk"
	"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 DiskIOInfo struct {
	Name                 string `json:"name"`
	ReadsCompletedBytes  uint64 `json:"reads_completed_bytes"`
	WritesCompletedBytes uint64 `json:"writes_completed_bytes"`
}
type MemoryInfo struct {
	Total      uint64 `json:"total_bytes"`
	Used       uint64 `json:"used_bytes"`
	Buffers    uint64 `json:"buffers_bytes"`
	Cached     uint64 `json:"cached_bytes"`
	Free       uint64 `json:"free_bytes"`
	Active     uint64 `json:"active_bytes"`
	Inactive   uint64 `json:"inactive_bytes"`
	SwapTotal  uint64 `json:"swap_total_bytes"`
	SwapUsed   uint64 `json:"swap_used_bytes"`
	SwapCached uint64 `json:"swap_cached_bytes"`
	SwapFree   uint64 `json:"swap_free_bytes"`
}
type LoadInfo struct {
	Avg1  float64 `json:"avg_1"`
	Avg5  float64 `json:"avg_5"`
	Avg15 float64 `json:"avg_15"`
}

type CPUInfo struct {
	User    float64 `json:"user"`
	Nice    float64 `json:"nice"`
	System  float64 `json:"system"`
	Idle    float64 `json:"idle"`
	Iowait  float64 `json:"iowait"`
	Irq     float64 `json:"irq"`
	Softirq float64 `json:"softirq"`
	Steal   float64 `json:"steal"`
}
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"`
	DiskIO map[string]DiskIOInfo `json:"disk_io"`

	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{}{
			{
				"user":    p.CPU.User,
				"nice":    p.CPU.Nice,
				"system":  p.CPU.System,
				"idle":    p.CPU.Idle,
				"iowait":  p.CPU.Iowait,
				"irq":     p.CPU.Irq,
				"softirq": p.CPU.Softirq,
				"steal":   p.CPU.Steal,
			},
		}
	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) MarshalDisk() (name string, tags []map[string]string, fields []map[string]interface{}) {
	name = "disk"
	tags = make([]map[string]string, 0)
	fields = make([]map[string]interface{}, 0)
	for name, info := range p.DiskIO {
		tags, fields =
			append(tags, map[string]string{
				"os":   runtime.GOOS,
				"name": name,
			}),
			append(fields, map[string]interface{}{
				"reads_completed_bytes":  info.ReadsCompletedBytes,
				"writes_completed_bytes": info.WritesCompletedBytes,
			})
	}
	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,
				"used_bytes":        p.Memory.Used,
				"buffers_bytes":     p.Memory.Buffers,
				"cached_bytes":      p.Memory.Cached,
				"free_bytes":        p.Memory.Free,
				"active_bytes":      p.Memory.Active,
				"inactive_bytes":    p.Memory.Inactive,
				"swap_total_bytes":  p.Memory.SwapTotal,
				"swap_used_bytes":   p.Memory.SwapUsed,
				"swap_cached_bytes": p.Memory.SwapCached,
				"swap_free_bytes":   p.Memory.SwapFree,
			},
		}
	return
}
func (p *OSMetricInfo) Applier() (generator []OSMetricPointGen) {
	generator = append(
		generator,
		p.MarshalLoad,
		p.MarshalCPU,
		p.MarshalMemory,
		p.MarshalNetworks,
		p.MarshalDisk,
		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 used: ", misc.FileSizeIEC(memoryInfo.Used))
	p.logger.Debugf("memory buffers: ", misc.FileSizeIEC(memoryInfo.Buffers))
	p.logger.Debugf("memory cached: ", misc.FileSizeIEC(memoryInfo.Cached))
	p.logger.Debugf("memory free: ", misc.FileSizeIEC(memoryInfo.Free))
	p.logger.Debugf("memory active: ", misc.FileSizeIEC(memoryInfo.Active))
	p.logger.Debugf("memory inactive: ", misc.FileSizeIEC(memoryInfo.Inactive))
	p.logger.Debugf("memory swapTotal: ", misc.FileSizeIEC(memoryInfo.SwapTotal))
	p.logger.Debugf("memory swapUsed: ", misc.FileSizeIEC(memoryInfo.SwapUsed))
	p.logger.Debugf("memory swapCached: ", misc.FileSizeIEC(memoryInfo.SwapCached))
	p.logger.Debugf("memory swapFree: ", misc.FileSizeIEC(memoryInfo.SwapFree))

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

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("cpu user: %d\n", ct.User)
	p.logger.Debugf("cpu nice: %d\n", ct.Nice)
	p.logger.Debugf("cpu system: %d\n", ct.System)
	p.logger.Debugf("cpu idle: %d\n", ct.Idle)
	p.logger.Debugf("cpu iowait: %d\n", ct.Iowait)
	p.logger.Debugf("cpu irq: %d\n", ct.Irq)
	p.logger.Debugf("cpu softirq: %d\n", ct.Softirq)
	p.logger.Debugf("cpu steal: %d\n", ct.Steal)
	p.logger.Debugf("cpu total: %d\n", ct.Total)

	p.logger.Debugf("cpu user: %f%%\n", float64(ct.User*100)/float64(ct.Total))
	p.logger.Debugf("cpu nice: %f%%\n", float64(ct.Nice*100)/float64(ct.Total))
	p.logger.Debugf("cpu system: %f%%\n", float64(ct.System*100)/float64(ct.Total))
	p.logger.Debugf("cpu idle: %f%%\n", float64(ct.Idle*100)/float64(ct.Total))
	p.logger.Debugf("cpu iowait: %f%%\n", float64(ct.Iowait*100)/float64(ct.Total))
	p.logger.Debugf("cpu irq: %f%%\n", float64(ct.Irq*100)/float64(ct.Total))
	p.logger.Debugf("cpu softirq: %f%%\n", float64(ct.Softirq*100)/float64(ct.Total))
	p.logger.Debugf("cpu steal: %f%%\n", float64(ct.Steal*100)/float64(ct.Total))

	info.CPU.User = float64(ct.User*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.Idle = float64(ct.Idle*100) / float64(ct.Total)
	info.CPU.Iowait = float64(ct.Iowait*100) / float64(ct.Total)
	info.CPU.Irq = float64(ct.Irq*100) / float64(ct.Total)
	info.CPU.Softirq = float64(ct.Softirq*100) / float64(ct.Total)
	info.CPU.Steal = float64(ct.Steal*100) / float64(ct.Total)

	return
}
func (p *osMetric) readDiskStat(info *OSMetricInfo, errChan chan<- error, waiter *sync.WaitGroup) {
	diskinfos, err := disk.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 readDiskStat: ", err)
			errChan <- err
		}
	}()
	if err != nil {
		return
	}

	diskIoMap := make(map[string]DiskIOInfo)
	for _, dsk := range diskinfos {
		p.logger.Debugf("disk name: ", dsk.Name)
		p.logger.Debugf("disk read: ", misc.FileSizeIEC(dsk.ReadsCompleted))
		p.logger.Debugf("disk written: ", misc.FileSizeIEC(dsk.WritesCompleted))
		diskIoMap[dsk.Name] = DiskIOInfo{
			Name:                 dsk.Name,
			ReadsCompletedBytes:  dsk.ReadsCompleted,
			WritesCompletedBytes: dsk.WritesCompleted,
		}
	}
	info.DiskIO = diskIoMap

	return
}

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