package tui

import (
	"fmt"
	"get-container/cmd/hytop/tchart"
	"get-container/utils"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	linkedlist "github.com/emirpasic/gods/v2/lists/doublylinkedlist"
	"github.com/lucasb-eyer/go-colorful"
	"github.com/muesli/gamut"
)

const (
	SysLoadHeight = 5   // 固定图表高度
	SysLoadWidth  = 77  // 固定图表宽度，不包含左右的边框
	SysLoadCap    = 600 // 记录
)

var (
	SysLoadBorder = LowLeightStyle.Render(`│
│
│
│
│`)
)

// ModelSysLoad 系统负载组件
type ModelSysLoad struct {
	SysMem              *MyTimeChart
	SysCPU              *MyTimeChart
	DCU                 *MyTimeChart
	DCUMem              *MyTimeChart
	dataMaplock         sync.RWMutex                               // 保护下面两个map的锁，防止并发读写
	dcuMemData          map[int]*linkedlist.List[tchart.TimePoint] // 记录dcu的内存使用信息点
	dcuUsgData          map[int]*linkedlist.List[tchart.TimePoint] // 记录dcu的使用率信息点
	current             *SysLoadInfo                               // 当前的全量信息
	topLine, topL       string
	bottomLine, bottomL string
	style               lipgloss.Style
	width               int              // 组件总宽度
	colors              []lipgloss.Color // 时序图的颜色表
	colorsLow           []lipgloss.Color // 低亮度模式的颜色表
	actionMsg           *ActionMsg       // 动作消息
	modelMsg            *ModelMsg        // 模型周期性消息
	DCUToShow           atomic.Int32     // 要显示的DCU信息的索引，如果为-1，表示显示dcu的平均数据

	dataNumToStore int // 维持DCU和DCUMem图表需要的最多数据点的数量
}

type SysLoadInfo struct {
	T                               time.Time
	DCUUsage                        map[int]float32
	DCUMemUsage                     map[int]float32
	Load1, Load5, Load15            float64
	MemTotal, SwapTotal             uint64
	MemUsed, SwapUsed               uint64
	MemUsedPercent, SwapUsedPercent float64
	CPUPercent                      float64
	DCUUsageAvg                     float32
	DCUMemUsageAvg                  float32
}

func NewModelSysLoad(width int) *ModelSysLoad {
	result := ModelSysLoad{}
	result.width = width
	result.dcuMemData = make(map[int]*linkedlist.List[tchart.TimePoint])
	result.dcuUsgData = make(map[int]*linkedlist.List[tchart.TimePoint])
	result.colors = ConvertColor(gamut.Blends(lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff"), SysLoadHeight))
	result.colorsLow = ConvertColor(gamut.Blends(gamut.Darker(lipgloss.Color("#ff0000"), 0.8), gamut.Darker(lipgloss.Color("#00ff00ff"), 0.8), SysLoadHeight))

	subLine := width - StaticWidth - 1
	result.dataNumToStore = subLine*2 + 1
	result.SysMem = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, result.colors)
	result.SysCPU = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, result.colors)
	result.DCU = NewTimeChart(subLine, SysLoadHeight, 0, 100, result.colors)
	result.DCUMem = NewTimeChart(subLine, SysLoadHeight, 0, 100, result.colors)
	result.topLine = myBorder.MiddleLeft + genXAxis(SysLoadWidth) + myBorder.Middle + genXAxis(subLine) + myBorder.MiddleRight
	result.bottomLine = "╞" + strings.Repeat(myBorder.Bottom, SysLoadWidth) + "╧" + strings.Repeat(myBorder.Bottom, subLine) + "╡"

	result.topL = LowLeightStyle.Render("├" + genXAxisNoStyle(SysLoadWidth) + "┼" + genXAxisNoStyle(subLine) + "┤")

	result.bottomL = LowLeightStyle.Render(result.bottomLine)
	result.style = lipgloss.NewStyle()
	result.DCUToShow.Store(-1)
	return &result
}

func (m *ModelSysLoad) Init() tea.Cmd {
	result := make([]tea.Cmd, 0)
	if c := m.DCU.Init(); c != nil {
		result = append(result, c)
	}
	if c := m.DCUMem.Init(); c != nil {
		result = append(result, c)
	}
	if c := m.SysMem.Init(); c != nil {
		result = append(result, c)
	}
	if c := m.SysCPU.Init(); c != nil {
		result = append(result, c)
	}
	sysInfo, _ := utils.GetSysInfo()
	s := SysLoadInfo{}
	s.Load1 = sysInfo.LoadAverage1
	s.Load5 = sysInfo.LoadAverage5
	s.Load15 = sysInfo.LoadAverage15
	s.MemTotal = sysInfo.MemTotal
	s.MemUsed = sysInfo.MemUsage
	s.SwapTotal = sysInfo.SwapTotal
	s.SwapUsed = sysInfo.SwapUsage
	s.MemUsedPercent = sysInfo.MemUsagePercent
	s.SwapUsedPercent = sysInfo.SwapUsagePercent
	s.T = time.Now()
	s.CPUPercent = sysInfo.CPUPercent
	s.DCUUsageAvg = 0
	s.DCUMemUsageAvg = 0
	m.current = &s
	return tea.Batch(result...)
}

func (m *ModelSysLoad) init() [6]time.Time {
	var result [6]time.Time
	result[0] = time.Now()
	m.DCU.Init()
	result[1] = time.Now()
	m.DCUMem.Init()
	result[2] = time.Now()
	m.SysMem.Init()
	result[3] = time.Now()
	m.SysCPU.Init()
	result[4] = time.Now()
	sysInfo, _ := utils.GetSysInfo()
	result[5] = time.Now()
	s := SysLoadInfo{}
	s.Load1 = sysInfo.LoadAverage1
	s.Load5 = sysInfo.LoadAverage5
	s.Load15 = sysInfo.LoadAverage15
	s.MemTotal = sysInfo.MemTotal
	s.MemUsed = sysInfo.MemUsage
	s.SwapTotal = sysInfo.SwapTotal
	s.SwapUsed = sysInfo.SwapUsage
	s.MemUsedPercent = sysInfo.MemUsagePercent
	s.SwapUsedPercent = sysInfo.SwapUsagePercent
	s.T = time.Now()
	s.CPUPercent = sysInfo.CPUPercent
	s.DCUUsageAvg = 0
	s.DCUMemUsageAvg = 0
	m.current = &s
	return result
}

func (m *ModelSysLoad) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := inputMsg.(type) {
	case *ModelMsg:
		m.modelMsg = msg
		m.updateInfo(msg)
		return m, nil
	case *ActionMsg:
		m.actionMsg = msg
		m.handleAction()
		return m, nil
	}
	return m, nil
}

// handleAction 处理ActionMsg
func (m *ModelSysLoad) handleAction() {
	if m.actionMsg == nil || m.actionMsg.VM != VMMain {
		return
	}
	targetIndex := int32(m.actionMsg.TargetDCUIndex)
	oldIndex := m.DCUToShow.Swap(targetIndex)
	if oldIndex == targetIndex {
		// 没有变
		return
	}
	wg := sync.WaitGroup{}
	wg.Add(1)
	index := int(targetIndex)
	rl := m.dataMaplock.RLocker()
	rl.Lock()
	mem := m.dcuMemData[index].Values()
	usage := m.dcuUsgData[index].Values()
	rl.Unlock()
	go func() {
		defer wg.Done()
		m3, _ := m.DCU.Update(NewTimeCharMsg(usage, true))
		m.DCU = m3.(*MyTimeChart)
	}()
	m4, _ := m.DCUMem.Update(NewTimeCharMsg(mem, true))
	m.DCUMem = m4.(*MyTimeChart)
	wg.Wait()
}

func (m *ModelSysLoad) View() string {
	darkMode := false
	if m.actionMsg != nil && m.actionMsg.Action != nil && m.actionMsg.VM == VMMain {
		darkMode = true
	}
	memUsed := utils.MemorySize{Num: m.current.MemUsed, Unit: utils.Byte}

	load := fmt.Sprintf(" Load Average: %.2f %.2f %.2f\n CPU: %.1f%%", m.current.Load1, m.current.Load5, m.current.Load15, m.current.CPUPercent)
	mem := fmt.Sprintf(" MEM: %s (%.1f%%)", memUsed.HumanReadStr(1), m.current.MemUsedPercent)
	if darkMode {
		load = LowLeightStyle.Render(load)
		mem = LowLeightStyle.Render(mem)
	}
	var dcuMem, dcu string
	if m.actionMsg == nil  {
		dcuMem = fmt.Sprintf(" DCU AVG MEM: %.1f%%", m.current.DCUMemUsageAvg)
		dcu = fmt.Sprintf(" DCU AVG UTL: %.1f%%", m.current.DCUUsageAvg)
	} else {
		index := m.actionMsg.TargetDCUIndex
		if index == -1 {
			dcuMem = fmt.Sprintf(" DCU AVG MEM: %.1f%%", m.current.DCUMemUsageAvg)
			dcu = fmt.Sprintf(" DCU AVG UTL: %.1f%%", m.current.DCUUsageAvg)
		} else {
			dcuMem = fmt.Sprintf(" DCU  %2d MEM: %.1f%%", index, m.current.DCUMemUsage[index])
			dcu = fmt.Sprintf(" DCU  %2d UTL: %.1f%%", index, m.current.DCUUsage[index])
		}
	}
	if darkMode {
		dcuMem = LowLeightStyle.Render(dcuMem)
		dcu = LowLeightStyle.Render(dcu)
	}
	if darkMode {
		load = StackPosition(load, m.SysCPU.ViewWithColor(m.colorsLow), lipgloss.Top, lipgloss.Left)
		mem = StackPosition(mem, m.SysMem.ViewWithColor(m.colorsLow), lipgloss.Bottom, lipgloss.Left)
		dcuMem = StackPosition(dcuMem, m.DCUMem.ViewWithColor(m.colorsLow), lipgloss.Top, lipgloss.Left)
		dcu = StackPosition(dcu, m.DCU.ViewWithColor(m.colorsLow), lipgloss.Bottom, lipgloss.Left)
		load = lipgloss.JoinHorizontal(lipgloss.Top, SysLoadBorder, load, SysLoadBorder)
		mem = lipgloss.JoinHorizontal(lipgloss.Top, SysLoadBorder, mem, SysLoadBorder)
		dcuMem = lipgloss.JoinHorizontal(lipgloss.Top, dcuMem, SysLoadBorder)
		dcu = lipgloss.JoinHorizontal(lipgloss.Top, dcu, SysLoadBorder)
	} else {
		load = StackPosition(load, m.SysCPU.View(), lipgloss.Top, lipgloss.Left)
		mem = StackPosition(mem, m.SysMem.View(), lipgloss.Bottom, lipgloss.Left)
		dcuMem = StackPosition(dcuMem, m.DCUMem.View(), lipgloss.Top, lipgloss.Left)
		dcu = StackPosition(dcu, m.DCU.View(), lipgloss.Bottom, lipgloss.Left)

		load = m.style.Border(myBorder, false, true, false).Render(load)
		mem = m.style.Border(myBorder, false, true, false).Render(mem)
		dcuMem = m.style.Border(myBorder, false, true, false, false).Render(dcuMem)
		dcu = m.style.Border(myBorder, false, true, false, false).Render(dcu)
	}
	up := lipgloss.JoinHorizontal(lipgloss.Top, load, dcuMem)
	down := lipgloss.JoinHorizontal(lipgloss.Top, mem, dcu)
	if darkMode {
		return lipgloss.JoinVertical(lipgloss.Left, up, m.topL, down, m.bottomL) + "\n"
	}
	return lipgloss.JoinVertical(lipgloss.Left, up, m.topLine, down, m.bottomLine) + "\n"
}

// updateInfo 向ModelSysLoad中添加数据
func (m *ModelSysLoad) updateInfo(t *ModelMsg) {
	var sysInfo *utils.SysInfo
	if t.systemInfo != nil {
		sysInfo = t.systemInfo
	} else {
		sysInfo, _ = utils.GetSysInfo()
	}
	s := SysLoadInfo{}
	s.Load1 = sysInfo.LoadAverage1
	s.Load5 = sysInfo.LoadAverage5
	s.Load15 = sysInfo.LoadAverage15
	s.MemTotal = sysInfo.MemTotal
	s.MemUsed = sysInfo.MemUsage
	s.SwapTotal = sysInfo.SwapTotal
	s.SwapUsed = sysInfo.SwapUsage
	s.MemUsedPercent = sysInfo.MemUsagePercent
	s.SwapUsedPercent = sysInfo.SwapUsagePercent
	s.T = t.t
	s.CPUPercent = sysInfo.CPUPercent
	s.DCUUsage = make(map[int]float32)
	s.DCUMemUsage = make(map[int]float32)
	s.DCUMemUsageAvg, s.DCUUsageAvg = 0, 0

	qinfo, lock := t.DCUInfo.GetQuitInfo()
	m.dataMaplock.Lock()
	for k, v := range qinfo {
		s.DCUMemUsageAvg += v.MemUsedPerent.OrElse(0.0)
		s.DCUMemUsage[k] = v.MemUsedPerent.OrElse(0.0)
		s.DCUUsageAvg += v.DCUUTil.OrElse(0.0)
		s.DCUUsage[k] = v.DCUUTil.OrElse(0.0)

		l1, have := m.dcuMemData[k]
		if !have {
			l1 = linkedlist.New[tchart.TimePoint]()
			m.dcuMemData[k] = l1
		}
		l1.Add(tchart.TimePoint{Time: t.t, Value: float64(v.MemUsedPerent.OrElse(0.0))})

		l2, have := m.dcuUsgData[k]
		if !have {
			l2 = linkedlist.New[tchart.TimePoint]()
			m.dcuUsgData[k] = l2
		}
		l2.Add(tchart.TimePoint{Time: t.t, Value: float64(v.DCUUTil.OrElse(0.0))})
	}
	l := len(qinfo)
	lock.Unlock()
	s.DCUMemUsageAvg /= float32(l)
	s.DCUUsageAvg /= float32(l)
	s.DCUMemUsage[-1] = s.DCUMemUsageAvg
	s.DCUUsage[-1] = s.DCUUsageAvg

	dcu, have := m.dcuUsgData[-1]
	if !have {
		dcu = linkedlist.New[tchart.TimePoint]()
		m.dcuUsgData[-1] = dcu
	}
	dcu.Add(tchart.TimePoint{Time: t.t, Value: float64(s.DCUUsageAvg)})

	mem, have := m.dcuMemData[-1]
	if !have {
		mem = linkedlist.New[tchart.TimePoint]()
		m.dcuMemData[-1] = mem
	}
	mem.Add(tchart.TimePoint{Time: t.t, Value: float64(s.DCUMemUsageAvg)})

	// 判断是否要丢弃多余的点
	if dcu.Size() > m.dataNumToStore {
		for _, v := range m.dcuMemData {
			v.Remove(0)
		}
		for _, v := range m.dcuUsgData {
			v.Remove(0)
		}
	}

	m.dataMaplock.Unlock()
	m.current = &s

	needUpdate, index := false, -1
	if m.actionMsg != nil  {
		index = m.actionMsg.TargetDCUIndex
		// 需要进入特定DCU的图表
		oldIndex := m.DCUToShow.Swap(int32(index))
		if oldIndex != int32(m.actionMsg.TargetDCUIndex) {
			// 需要更新
			needUpdate = true
		}
	} else {
		oldIndex := m.DCUToShow.Swap(-1)
		if oldIndex != -1 {
			// 需要更新
			needUpdate = true
		}
	}
	wg := sync.WaitGroup{}
	wg.Add(4)
	go func() {
		defer wg.Done()
		m1, _ := m.SysMem.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: s.MemUsedPercent}}, false))
		m.SysMem = m1.(*MyTimeChart)
	}()
	go func() {
		defer wg.Done()
		m2, _ := m.SysCPU.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: s.CPUPercent}}, false))
		m.SysCPU = m2.(*MyTimeChart)
	}()

	if needUpdate {
		// 准备全量数据
		rl := m.dataMaplock.RLocker()
		rl.Lock()
		mem := m.dcuMemData[index].Values()
		usage := m.dcuUsgData[index].Values()
		rl.Unlock()
		go func() {
			defer wg.Done()
			m3, _ := m.DCU.Update(NewTimeCharMsg(usage, true))
			m.DCU = m3.(*MyTimeChart)
		}()
		go func() {
			defer wg.Done()
			m4, _ := m.DCUMem.Update(NewTimeCharMsg(mem, true))
			m.DCUMem = m4.(*MyTimeChart)
		}()
	} else {
		// 无需更新，仅追加数据
		var valM, valU float32
		vU, haveU := s.DCUUsage[index]
		vM, haveM := s.DCUMemUsage[index]
		if haveU {
			valU = vU
		} else {
			valU = 0
		}
		if haveM {
			valM = vM
		} else {
			valM = 0
		}
		go func() {
			defer wg.Done()
			m3, _ := m.DCU.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: float64(valU)}}, false))
			m.DCU = m3.(*MyTimeChart)
		}()
		go func() {
			defer wg.Done()
			m4, _ := m.DCUMem.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: float64(valM)}}, false))
			m.DCUMem = m4.(*MyTimeChart)
		}()
	}
	wg.Wait()
}

func (m *ModelSysLoad) RanderColor(str string) string {
	lines := strings.Split(strings.Trim(str, "\n"), "\n")
	result := ""
	for k, line := range lines {
		c, _ := colorful.MakeColor(m.colors[k])
		result += lipgloss.NewStyle().Foreground(lipgloss.Color(c.Hex())).Render(line) + "\n"
	}
	return result
}
