package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/redis/go-redis/v9"
)

const URL = "http://wl.zhitongcn.com/yjiot/vue/customer/searchCharge?iccid=%s"

// Redis配置
const (
	RedisAddr     = "localhost:6379"
	RedisPassword = ""
	RedisDB       = 0
	RedisKey      = "flow_history"
)

type Res struct {
	Resdata struct {
		Qrycard struct {
			FlowUsed   float64 `json:"flow_used"`
			FlowLeft   float64 `json:"flow_left"`
			UpdateTime string  `json:"updatetime"` // 形如 2026-03-10 13:03:56
		}
	} `json:"resdata"`
}

// DataPoint 存储每次检查的数据点
type DataPoint struct {
	Used       float64   `json:"used"`
	Left       float64   `json:"left"`
	UpdateTime string    `json:"updateTime"`
	Timestamp  time.Time `json:"timestamp"`
}

var (
	rdb        *redis.Client
	history    []DataPoint
	lastUpdate string

	// 极致压缩的移动端优先 HTML 模板，新增了每日消耗柱状图
	htmlTemplate = `<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0">
<title>流量监控</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
*{box-sizing:border-box}
body{font-family:system-ui,-apple-system,sans-serif;margin:0;padding:12px;background:#f0f2f5;color:#333;overflow-x:hidden}
.c{background:#fff;border-radius:12px;padding:16px;margin-bottom:12px;box-shadow:0 2px 8px rgba(0,0,0,.04);width:100%}
.f{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px}
.v{font-size:24px;font-weight:700}
.r{color:#ff6384}.b{color:#36a2eb}
.t{font-size:12px;color:#888;text-align:right}
.ch{position:relative;height:320px;width:100%}
.title{font-size:14px;font-weight:600;margin-bottom:8px;text-align:center;color:#666}
</style>
</head><body>
<div class="c">
<div class="f"><span class="r">已用 <span class="v">{{printf "%.2f" .Latest.Used}}</span>M</span><span class="b">剩余 <span class="v">{{printf "%.2f" .Latest.Left}}</span>M</span></div>
<div class="t">更新于: {{.Latest.UpdateTime}}</div>
</div>
<!-- 实时趋势折线图 -->
<div class="c ch">
<div class="title">实时流量趋势</div>
<canvas id="c"></canvas>
</div>
<!-- 每日消耗柱状图 -->
<div class="c ch">
<div class="title">每日流量消耗 (M)</div>
<canvas id="dailyChart"></canvas>
</div>
<script>
// 渲染折线图
new Chart(document.getElementById('c'),{
type:'line',
data:{
labels:{{.Timestamps}},
datasets:[
{label:'已用(M)',data:{{.UsedData}},borderColor:'#ff6384',backgroundColor:'rgba(255,99,132,0.1)',tension:0.3,fill:true,pointRadius:0,borderWidth:2},
{label:'剩余(M)',data:{{.LeftData}},borderColor:'#36a2eb',backgroundColor:'rgba(54,162,235,0.1)',tension:0.3,fill:true,pointRadius:0,borderWidth:2}
]},
options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},scales:{x:{ticks:{maxTicksLimit:6,maxRotation:0}},y:{beginAtZero:true}},plugins:{legend:{position:'bottom',labels:{boxWidth:12}}}}
});

// 渲染柱状图
new Chart(document.getElementById('dailyChart'),{
type:'bar',
data: {
labels:{{.DailyTime}},
datasets:[
{label:'当日消耗(M)',data:{{.DailyUsage}},backgroundColor:'rgba(54,162,235,0.5)',borderColor:'#36a2eb',borderWidth:1}
]},
options:{responsive:true,maintainAspectRatio:false,scales:{y:{beginAtZero:true}},plugins:{legend:{position:'bottom',labels:{boxWidth:12}}}}
});
</script>
</body></html>`

	IccId  = flag.String("iccid", "", "iccid")
	OpenId = flag.String("openid", "", "openid")
	AppId  = flag.String("appid", "", "appid")
)

func Send() (used, left float64, updateTime string) {
	req, err := http.NewRequest("GET", fmt.Sprintf(URL, *IccId), nil)
	if err != nil {
		log.Printf("HTTP请求失败: %v", err)
		return
	}
	req.Header.Set("OpenId", *OpenId)
	req.Header.Set("Appid", *AppId)

	// 建议后续加上超时控制，例如：client := &http.Client{Timeout: 10 * time.Second}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Printf("HTTP请求失败: %v", err)
		return
	}
	defer res.Body.Close()

	var r Res
	if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
		log.Printf("JSON解码失败: %v", err)
		return
	}

	return r.Resdata.Qrycard.FlowUsed, r.Resdata.Qrycard.FlowLeft, r.Resdata.Qrycard.UpdateTime
}

// generateHTML 生成包含图表的HTML文件
func generateHTML() error {
	if len(history) == 0 {
		return nil
	}

	// 准备数据
	var timestamps []string
	var usedData []float64
	var leftData []float64

	// ========== 每天流量变化计算逻辑 ==========
	lastRecordPerDay := make(map[string]float64)
	var orderedDates []string

	for _, dp := range history {
		timeStr := dp.UpdateTime

		// 1. 每日用量统计前置提取日期
		if len(timeStr) >= 10 {
			date := timeStr[:10] // 截取 YYYY-MM-DD
			if _, ok := lastRecordPerDay[date]; !ok {
				orderedDates = append(orderedDates, date)
			}
			// 循环覆盖，最终保留的就是该日期下的最后一条记录
			lastRecordPerDay[date] = dp.Used
		}

		// 2. 为折线图截取 MM-DD HH:MM，如 "2026-03-10 13:03:56" -> "03-10 13:03"
		if len(timeStr) >= 16 {
			timeStr = timeStr[5:16]
		}

		timestamps = append(timestamps, timeStr)
		usedData = append(usedData, dp.Used)
		leftData = append(leftData, dp.Left)
	}

	var dailyTimestamps []string
	var dailyChanges []float64

	// 计算每天的差值
	if len(history) > 0 {
		firstRecordUsed := history[0].Used
		for i, date := range orderedDates {
			dailyTimestamps = append(dailyTimestamps, date[5:10]) // X轴展示 MM-DD
			if i == 0 {
				// 第一天：当日最后记录 - 历史第一条记录
				dailyChanges = append(dailyChanges, lastRecordPerDay[date]-firstRecordUsed)
			} else {
				// 往后每天：当日最后记录 - 昨天最后记录
				prevDate := orderedDates[i-1]
				dailyChanges = append(dailyChanges, lastRecordPerDay[date]-lastRecordPerDay[prevDate])
			}
		}
	}

	// 将 Go 的切片序列化为 JSON 字符串
	tJSON, _ := json.Marshal(timestamps)
	uJSON, _ := json.Marshal(usedData)
	lJSON, _ := json.Marshal(leftData)
	dTimeJSON, _ := json.Marshal(dailyTimestamps)
	dChangeJSON, _ := json.Marshal(dailyChanges)

	// 准备模板数据
	data := struct {
		Timestamps template.JS
		UsedData   template.JS
		LeftData   template.JS
		DailyTime  template.JS
		DailyUsage template.JS
		Latest     DataPoint
	}{
		Timestamps: template.JS(tJSON),
		UsedData:   template.JS(uJSON),
		LeftData:   template.JS(lJSON),
		DailyTime:  template.JS(dTimeJSON),
		DailyUsage: template.JS(dChangeJSON),
		Latest:     history[len(history)-1],
	}

	// 创建模板
	tmpl, err := template.New("chart").Parse(htmlTemplate)
	if err != nil {
		return fmt.Errorf("解析模板失败: %v", err)
	}

	// 创建文件
	file, err := os.Create("/opt/flow_chart.html")
	if err != nil {
		return fmt.Errorf("创建文件失败: %v", err)
	}
	defer file.Close()

	// 执行模板
	if err := tmpl.Execute(file, data); err != nil {
		return fmt.Errorf("生成HTML失败: %v", err)
	}

	log.Printf("已生成移动端适配的流量图表: /opt/flow_chart.html")
	return nil
}

// loadHistoryFromRedis 从Redis加载历史数据
func loadHistoryFromRedis(ctx context.Context) error {
	vals, err := rdb.LRange(ctx, RedisKey, 0, -1).Result()
	if err != nil {
		return err
	}

	history = nil

	for _, val := range vals {
		var dp DataPoint
		if err := json.Unmarshal([]byte(val), &dp); err != nil {
			log.Printf("解析数据点失败: %v (数据: %s)", err, val)
			continue
		}
		history = append(history, dp)
	}

	log.Printf("从Redis加载了 %d 个历史数据点", len(history))
	return nil
}

// saveDataPointToRedis 保存数据点到Redis
func saveDataPointToRedis(ctx context.Context, dp DataPoint) error {
	data, err := json.Marshal(dp)
	if err != nil {
		return err
	}

	if err := rdb.RPush(ctx, RedisKey, data).Err(); err != nil {
		return err
	}

	if err := rdb.LTrim(ctx, RedisKey, -10000, -1).Err(); err != nil {
		return err
	}

	return nil
}

func main() {
	flag.Parse()

	rdb = redis.NewClient(&redis.Options{
		Addr:     RedisAddr,
		Password: RedisPassword,
		DB:       RedisDB,
	})

	ctx := context.Background()
	if err := rdb.Ping(ctx).Err(); err != nil {
		log.Fatalf("连接Redis失败: %v", err)
	}
	defer rdb.Close()

	if err := loadHistoryFromRedis(ctx); err != nil {
		log.Fatalf("加载历史数据失败: %v", err)
	}

	if len(history) > 0 {
		lastUpdate = history[len(history)-1].UpdateTime
	}

	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

	ticker := time.NewTicker(time.Minute * 8)
	defer ticker.Stop()

	// 启动时也主动执行一次HTML生成，确保程序重启后HTML内容也是最新的
	generateHTML()

	for {
		select {
		case <-ticker.C:
			used, left, updateTime := Send()
			if used == 0.0 && left == 0.0 && updateTime == "" {
				continue
			}

			dataPoint := DataPoint{
				Used:       used,
				Left:       left,
				UpdateTime: updateTime,
				Timestamp:  time.Now(),
			}

			if updateTime != lastUpdate {
				log.Printf("检测到更新时间变更: %s -> %s", lastUpdate, updateTime)
				lastUpdate = updateTime

				history = append(history, dataPoint)

				if len(history) > 10000 {
					history = history[len(history)-10000:]
				}

				if err := saveDataPointToRedis(ctx, dataPoint); err != nil {
					log.Printf("保存数据点到Redis失败: %v", err)
				}

				if err := generateHTML(); err != nil {
					log.Printf("生成图表失败: %v", err)
				}
			} else {
				log.Printf("流量使用: %.2f, 剩余: %.2f, 更新时间: %s (未变化)", used, left, updateTime)
			}

		case <-sigCh:
			log.Println("接收到退出信号，正在关闭...")
			cancel()
			return
		case <-ctx.Done():
			return
		}
	}
}
