package tencentim

import (
	"compress/gzip"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	// historyFileCommand 获取一个自然小时的 C2C 消息归档下载地址；它是离线统计接口，
	// 不进入客户端发消息链路，也不会把业务服务延迟传播到腾讯 IM。
	historyFileCommand = "v4/open_msg_svc/get_history"
	// 小时归档通常是 MB 级文件，不能复用实时 REST 调用的 5 秒传输预算；
	// cron RPC 仍有 2 分钟总 deadline，这里只放宽单文件下载而不影响发消息接口。
	historyDownloadTimeout = 60 * time.Second
)

var tencentHistoryLocation = time.FixedZone("UTC+8", 8*60*60)

// C2CHistoryFile 是腾讯 IM 返回的短期下载凭据。URL 含临时授权信息，只能在内存中使用，
// 调用方不得把它写入日志、数据库或业务错误。
type C2CHistoryFile struct {
	URL      string `json:"URL"`
	FileSize int64  `json:"FileSize"`
	GzipSize int64  `json:"GzipSize"`
}

// C2CHistoryMessage 只解析私聊人数统计所需字段。MsgBody 等正文由流式 decoder 跳过，
// 避免消息内容进入 user-service 内存对象、日志或统计库。
type C2CHistoryMessage struct {
	FromAccount string `json:"From_Account"`
	ToAccount   string `json:"To_Account"`
	Timestamp   int64  `json:"MsgTimestamp"`
	Sequence    uint64 `json:"MsgSeq"`
	Random      uint64 `json:"MsgRandom"`
}

type historyFilesRequest struct {
	ChatType string `json:"ChatType"`
	MsgTime  string `json:"MsgTime"`
}

type historyFilesResponse struct {
	restResponse
	Files []C2CHistoryFile `json:"File"`
}

// ListC2CHistoryFiles 获取指定 UTC 小时对应的 C2C 归档。
// 腾讯接口的 MsgTime 使用 UTC+8 小时字符串，而消息本身仍携带 Unix 时间戳；在 REST
// 边界集中转换可防止 cron 把 UTC 小时错取成前一天的数据。
func (c *RESTClient) ListC2CHistoryFiles(ctx context.Context, hourStartUTC time.Time) ([]C2CHistoryFile, error) {
	hourStartUTC = hourStartUTC.UTC().Truncate(time.Hour)
	request := historyFilesRequest{
		ChatType: "C2C",
		MsgTime:  hourStartUTC.In(tencentHistoryLocation).Format("2006010215"),
	}
	var response historyFilesResponse
	if err := c.post(ctx, historyFileCommand, request, &response); err != nil {
		return nil, err
	}
	if err := response.err(); err != nil {
		return nil, err
	}

	files := make([]C2CHistoryFile, 0, len(response.Files))
	for _, file := range response.Files {
		file.URL = strings.TrimSpace(file.URL)
		if file.URL == "" {
			continue
		}
		files = append(files, file)
	}
	return files, nil
}

// StreamC2CHistory 下载并逐条解码一个 gzip 归档。decoder 只保留当前消息的统计字段，
// 即使单小时归档很大也不会把整个 MsgList 或消息正文一次性加载进内存。
func (c *RESTClient) StreamC2CHistory(ctx context.Context, file C2CHistoryFile, consume func(C2CHistoryMessage) error) (int, error) {
	if c == nil || c.httpClient == nil {
		return 0, fmt.Errorf("tencent im rest client is not configured")
	}
	if consume == nil {
		return 0, fmt.Errorf("tencent im history consumer is required")
	}
	downloadURL, err := url.Parse(strings.TrimSpace(file.URL))
	if err != nil || downloadURL.Scheme != "https" || downloadURL.Host == "" {
		return 0, fmt.Errorf("tencent im history download url is invalid")
	}
	request, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL.String(), nil)
	if err != nil {
		return 0, fmt.Errorf("build tencent im history request: %w", err)
	}
	downloadClient := *c.httpClient
	if downloadClient.Timeout <= 0 || downloadClient.Timeout < historyDownloadTimeout {
		downloadClient.Timeout = historyDownloadTimeout
	}
	response, err := downloadClient.Do(request)
	if err != nil {
		var transportErr *url.Error
		if errors.As(err, &transportErr) {
			// url.Error 会把带临时下载签名的完整 URL 拼进错误；只保留底层网络原因。
			return 0, fmt.Errorf("tencent im history download failed: %w", transportErr.Err)
		}
		return 0, fmt.Errorf("tencent im history download failed: %w", err)
	}
	defer response.Body.Close()
	if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
		return 0, fmt.Errorf("tencent im history download status %d", response.StatusCode)
	}

	archive, err := gzip.NewReader(response.Body)
	if err != nil {
		return 0, fmt.Errorf("open tencent im history gzip: %w", err)
	}
	defer archive.Close()

	decoder := json.NewDecoder(archive)
	first, err := decoder.Token()
	if err != nil {
		return 0, fmt.Errorf("decode tencent im history document: %w", err)
	}
	if delimiter, ok := first.(json.Delim); !ok || delimiter != '{' {
		return 0, fmt.Errorf("tencent im history document is not an object")
	}

	processed := 0
	for decoder.More() {
		keyToken, err := decoder.Token()
		if err != nil {
			return processed, fmt.Errorf("decode tencent im history field: %w", err)
		}
		key, ok := keyToken.(string)
		if !ok {
			return processed, fmt.Errorf("tencent im history field name is invalid")
		}
		if key != "MsgList" {
			// 元数据字段与统计无关；RawMessage 只短暂承载单个标量/小对象，不读取消息正文。
			var ignored json.RawMessage
			if err := decoder.Decode(&ignored); err != nil {
				return processed, fmt.Errorf("skip tencent im history metadata: %w", err)
			}
			continue
		}

		listStart, err := decoder.Token()
		if err != nil {
			return processed, fmt.Errorf("decode tencent im history list: %w", err)
		}
		if delimiter, ok := listStart.(json.Delim); !ok || delimiter != '[' {
			return processed, fmt.Errorf("tencent im history MsgList is not an array")
		}
		for decoder.More() {
			var message C2CHistoryMessage
			if err := decoder.Decode(&message); err != nil {
				return processed, fmt.Errorf("decode tencent im history message: %w", err)
			}
			if err := consume(message); err != nil {
				return processed, err
			}
			processed++
		}
		if _, err := decoder.Token(); err != nil {
			return processed, fmt.Errorf("close tencent im history list: %w", err)
		}
	}
	if _, err := decoder.Token(); err != nil {
		return processed, fmt.Errorf("close tencent im history document: %w", err)
	}
	return processed, nil
}
