package agentd

import (
	"context"
	"encoding/json"
	"net/http"
	"sync"
	"time"

	agentv1 "github.com/wangjia/pangolin/server/internal/pb/agentv1"
)

// ClashUsageSource reads the node's cumulative traffic from sing-box's clash_api
// (/connections downloadTotal/uploadTotal) and reports per-window deltas.
//
// Limitation: the clash API exposes only NODE-TOTAL bytes (no per-user field, and
// v2ray_api is not compiled into this sing-box build). So node-total delta is
// split evenly across currently provisioned dp_uuids — exact for a single active
// user, approximate for multiple. See todo #7 for accurate accounting (rebuild
// sing-box with with_v2ray_api).
type ClashUsageSource struct {
	sb     *SingBox
	addr   string
	secret string
	client *http.Client

	mu               sync.Mutex
	inited           bool
	lastUp, lastDown int64
}

func newClashUsageSource(sb *SingBox) *ClashUsageSource {
	return &ClashUsageSource{
		sb:     sb,
		addr:   clashAPIAddr,
		secret: clashAPISecret,
		client: &http.Client{Timeout: 5 * time.Second},
	}
}

type clashConnections struct {
	DownloadTotal int64 `json:"downloadTotal"`
	UploadTotal   int64 `json:"uploadTotal"`
}

// Collect returns per-dp_uuid usage accumulated since the previous call.
// First call only sets the baseline (returns nil).
func (c *ClashUsageSource) Collect() []*agentv1.UsageEntry {
	up, down, ok := c.fetchTotals()
	if !ok {
		return nil
	}

	c.mu.Lock()
	defer c.mu.Unlock()
	if !c.inited {
		c.lastUp, c.lastDown, c.inited = up, down, true
		return nil
	}
	dUp, dDown := up-c.lastUp, down-c.lastDown
	c.lastUp, c.lastDown = up, down
	if dUp < 0 || dDown < 0 {
		// Counters reset (sing-box restarted) → skip this window.
		return nil
	}
	if dUp == 0 && dDown == 0 {
		return nil
	}

	uuids := c.sb.DpUUIDs()
	if len(uuids) == 0 {
		return nil
	}
	n := int64(len(uuids))
	perUp, perDown := dUp/n, dDown/n
	entries := make([]*agentv1.UsageEntry, 0, len(uuids))
	for _, u := range uuids {
		entries = append(entries, &agentv1.UsageEntry{
			DpUUID:         u,
			BytesUp:        perUp,
			BytesDown:      perDown,
			SessionMinutes: 1, // 本窗口有流量 → 计 1 分钟在线(近似)
		})
	}
	return entries
}

func (c *ClashUsageSource) fetchTotals() (up, down int64, ok bool) {
	req, err := http.NewRequestWithContext(
		context.Background(), http.MethodGet, "http://"+c.addr+"/connections", nil)
	if err != nil {
		return 0, 0, false
	}
	if c.secret != "" {
		req.Header.Set("Authorization", "Bearer "+c.secret)
	}
	resp, err := c.client.Do(req)
	if err != nil {
		return 0, 0, false
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return 0, 0, false
	}
	var cc clashConnections
	if err := json.NewDecoder(resp.Body).Decode(&cc); err != nil {
		return 0, 0, false
	}
	// clash 的 up/down 是「代理视角」:为客户端下载,代理需把数据 upload 给客户端,
	// 故 clash.uploadTotal ≈ 用户下载、clash.downloadTotal ≈ 用户上传 → 此处对调,
	// 返回「用户视角」的 (up, down)。
	return cc.DownloadTotal, cc.UploadTotal, true
}
