package tui

import (
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"get-container/cmd/hytop/tchart"

	"github.com/NimbleMarkets/ntcharts/canvas/runes"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	zone "github.com/lrstanley/bubblezone"
)

const (
	A = "├"
)

var (
	axisFStyle = lipgloss.NewStyle().Inline(true).Foreground(lipgloss.Color("#4d4d4dff"))
)

// genXAxis 生成X轴，参数l是x轴的长度
func genXAxis(l int) string {
	t60 := l / 30
	t30 := l >= 18
	var result string
	if t30 {
		result = A + strings.Repeat("─", 14)
		result = axisFStyle.Render("30s") + result
	} else {
		return strings.Repeat("─", l)
	}
	// 长度不超过33
	if l < 33 {
		return strings.Repeat("─", l-18) + result
	}
	for i := 1; i <= t60+1; i++ {
		timeStr := strconv.Itoa(i*60) + "s"
		timeStrLen := len(timeStr)
		timeStr = axisFStyle.Render(timeStr)
		targetLen := timeStrLen + i*30
		if l < targetLen {
			// 不渲染标记，仅增加轴长度
			result = strings.Repeat("─", l-lipgloss.Width(result)) + result
			break
		}
		// 渲染标记
		result = timeStr + A + strings.Repeat("─", targetLen-lipgloss.Width(result)-timeStrLen-1) + result
	}
	return result
}

// MyTimeChartMsg 时间流表消息，用于插入数据
type MyTimeChartMsg struct {
	Points map[string][]tchart.TimePoint
}

// MyTimeChart 特化的时间流表，时间区域就是宽度的2倍，单位是秒
type MyTimeChart struct {
	chart         *tchart.Model                 // 原始图表
	zM            *zone.Manager                 // 区域管理
	dataSet       map[string][]tchart.TimePoint // 数据点
	width, height int                           // 图表的高度和宽度
	max, min      float64                       // y轴的最值
	dataStyle     map[string]lipgloss.Style     // 记录每个数据集的样式
	lockDataSet   sync.RWMutex                  // 保护dataSet的并发读写
	lockChart     sync.RWMutex                  // 保护chart的数据集的并发读写
}

// New 新建图表，其中dataSet的Key为数据集名称，value为数据集的颜色
func NewTimeChart(width, height int, vmin, vmax float64, dataSet map[string]lipgloss.Color) *MyTimeChart {
	result := MyTimeChart{}
	result.max = vmax
	result.min = vmin
	result.width = width
	result.height = height
	zoneManager := zone.New()
	result.zM = zoneManager
	result.dataSet = make(map[string][]tchart.TimePoint)
	result.dataStyle = make(map[string]lipgloss.Style)
	result.lockChart = sync.RWMutex{}
	result.lockDataSet = sync.RWMutex{}
	result.lockDataSet.Lock()
	for k, v := range dataSet {
		result.dataSet[k] = make([]tchart.TimePoint, (result.width*2)+1)
		result.dataStyle[k] = lipgloss.NewStyle().Foreground(v)
	}
	initPoints := make([]tchart.TimePoint, (result.width*2)+1)
	// 准备数据，数据间隔为1秒
	now := time.Now()
	for i := result.width * 2; i >= 0; i-- {
		initPoints[i] = tchart.TimePoint{Time: now.Add(time.Duration(-i) * time.Second), Value: vmin}
	}
	for k := range dataSet {
		copy(result.dataSet[k], initPoints)
	}
	result.lockDataSet.Unlock()
	s := tchart.New(width, height+1,
		tchart.WithLineStyle(runes.ThinLineStyle),
		tchart.WithZoneManager(zoneManager),
		tchart.WithYRange(vmin, vmax),
		tchart.WithXYSteps(0, 0),
	)
	wg := sync.WaitGroup{}
	wg.Add(len(dataSet))
	for k := range dataSet {
		points := result.dataSet[k]
		style := result.dataStyle[k]
		go func(ps []tchart.TimePoint, st lipgloss.Style, key string) {
			result.lockChart.Lock()
			s.SetDataSet(key, ps)
			s.SetDataSetStyle(key, st)
			result.lockChart.Unlock()
			wg.Done()
		}(points, style, k)
	}
	wg.Wait()
	result.chart = &s
	return &result
}

func (m *MyTimeChart) PutPoint(points map[string][]tchart.TimePoint) {
	// 更新chart
	s := tchart.New(m.width, m.height+1,
		tchart.WithLineStyle(runes.ThinLineStyle),
		tchart.WithZoneManager(m.zM),
		tchart.WithYRange(m.min, m.max),
		tchart.WithXYSteps(0, 0),
	)
	m.chart = &s
	wg := sync.WaitGroup{}
	wg.Add(len(m.dataSet))
	keys, index := make([]string, len(m.dataSet)), 0
	for k := range m.dataSet {
		keys[index] = k
		index++
	}
	for _, k := range keys {
		newPoints := points[k]
		oldPoints := m.dataSet[k]
		go func(ops, nps []tchart.TimePoint, ds map[string][]tchart.TimePoint, key string) {
			ops = append(ops, nps...)
			sort.Slice(ops, func(i, j int) bool {
				return ops[i].Time.Before(ops[j].Time)
			})
			threshold := time.Now().Add(time.Second * time.Duration(-2*m.width))
			targetIndex := 0
			for i, p := range ops {
				if !p.Time.Before(threshold) {
					targetIndex = i
					break
				}
			}
			nps = append(make([]tchart.TimePoint, 0), ops[targetIndex:]...)
			m.lockDataSet.Lock()
			ds[key] = nps
			m.lockDataSet.Unlock()
			m.lockChart.Lock()
			s.SetDataSet(key, nps)
			s.SetDataSetStyle(key, m.dataStyle[key])
			m.lockChart.Unlock()
			wg.Done()
		}(oldPoints, newPoints, m.dataSet, k)
	}
	wg.Wait()
	m.chart.DrawXYAxisAndLabel()
	m.chart.DrawBrailleAll()
}

func (m *MyTimeChart) Init() tea.Cmd {
	m.chart.DrawXYAxisAndLabel()
	m.chart.DrawBrailleAll()
	return nil
}

func (m *MyTimeChart) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := inputMsg.(type) {
	case MyTimeChartMsg:
		m.PutPoint(msg.Points)
		return m, nil
	}
	return m, nil
}

func (m *MyTimeChart) View() string {
	rl := m.lockChart.RLocker()
	rl.Lock()
	str := m.zM.Scan(m.chart.View())
	rl.Unlock()
	return str[m.width+1:]
}
