package ilink

import (
	"bytes"
	"context"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	DefaultBaseURL       = "https://ilinkai.weixin.qq.com"
	DefaultBotType       = "3"
	DefaultBotAgent      = "CyberStrikeAI/1.0"
	ILinkAppID           = "bot"
	QRLongPollTimeout    = 35 * time.Second
	APIDefaultTimeout    = 15 * time.Second
	GetUpdatesTimeout    = 35 * time.Second
)

// Client 微信 iLink Bot HTTP 客户端（与 @tencent-weixin/openclaw-weixin 协议兼容）
type Client struct {
	BaseURL       string
	BotToken      string
	BotAgent      string
	ClientVersion uint32
	HTTP          *http.Client
}

func NewClient(baseURL, botToken, botAgent string, clientVersion uint32) *Client {
	base := strings.TrimSpace(baseURL)
	if base == "" {
		base = DefaultBaseURL
	}
	agent := strings.TrimSpace(botAgent)
	if agent == "" {
		agent = DefaultBotAgent
	}
	return &Client{
		BaseURL:       strings.TrimRight(base, "/"),
		BotToken:      strings.TrimSpace(botToken),
		BotAgent:      sanitizeBotAgent(agent),
		ClientVersion: clientVersion,
		HTTP:          &http.Client{Timeout: 0},
	}
}

// BuildClientVersion 将 semver 编码为 iLink-App-ClientVersion（0x00MMNNPP）
func BuildClientVersion(version string) uint32 {
	parts := strings.Split(version, ".")
	parse := func(i int) int {
		if i >= len(parts) {
			return 0
		}
		n, _ := strconv.Atoi(strings.TrimSpace(parts[i]))
		if n < 0 {
			return 0
		}
		return n
	}
	major := parse(0) & 0xff
	minor := parse(1) & 0xff
	patch := parse(2) & 0xff
	return uint32((major << 16) | (minor << 8) | patch)
}

type baseInfo struct {
	ChannelVersion string `json:"channel_version"`
	BotAgent       string `json:"bot_agent"`
}

func (c *Client) buildBaseInfo() baseInfo {
	return baseInfo{
		ChannelVersion: "1.0.0",
		BotAgent:       c.BotAgent,
	}
}

func randomWechatUIN() string {
	var b [4]byte
	_, _ = rand.Read(b[:])
	u := uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
	return base64.StdEncoding.EncodeToString([]byte(strconv.FormatUint(uint64(u), 10)))
}

func (c *Client) commonHeaders() http.Header {
	h := http.Header{}
	h.Set("iLink-App-Id", ILinkAppID)
	h.Set("iLink-App-ClientVersion", strconv.FormatUint(uint64(c.ClientVersion), 10))
	return h
}

func (c *Client) authHeaders() http.Header {
	h := c.commonHeaders()
	h.Set("Content-Type", "application/json")
	h.Set("AuthorizationType", "ilink_bot_token")
	h.Set("X-WECHAT-UIN", randomWechatUIN())
	if c.BotToken != "" {
		h.Set("Authorization", "Bearer "+c.BotToken)
	}
	return h
}

func (c *Client) endpointURL(path string) (string, error) {
	u, err := url.Parse(c.BaseURL + "/")
	if err != nil {
		return "", err
	}
	ref, err := url.Parse(path)
	if err != nil {
		return "", err
	}
	return u.ResolveReference(ref).String(), nil
}

func (c *Client) doRequest(ctx context.Context, method, path string, body []byte, headers http.Header, timeout time.Duration) ([]byte, error) {
	reqURL, err := c.endpointURL(path)
	if err != nil {
		return nil, err
	}
	var bodyReader io.Reader
	if len(body) > 0 {
		bodyReader = bytes.NewReader(body)
	}
	req, err := http.NewRequestWithContext(ctx, method, reqURL, bodyReader)
	if err != nil {
		return nil, err
	}
	for k, vs := range headers {
		for _, v := range vs {
			req.Header.Add(k, v)
		}
	}
	client := c.HTTP
	if client == nil {
		client = http.DefaultClient
	}
	if timeout > 0 {
		ctx2, cancel := context.WithTimeout(ctx, timeout)
		defer cancel()
		req = req.WithContext(ctx2)
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	raw, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("ilink %s %s: %d %s", method, path, resp.StatusCode, string(raw))
	}
	return raw, nil
}

// QRCodeResponse 获取二维码响应
type QRCodeResponse struct {
	QRCode           string `json:"qrcode"`
	QRCodeImgContent string `json:"qrcode_img_content"`
}

// GetBotQRCode 获取绑定二维码
func (c *Client) GetBotQRCode(ctx context.Context, botType string, localTokenList []string) (*QRCodeResponse, error) {
	if strings.TrimSpace(botType) == "" {
		botType = DefaultBotType
	}
	body, _ := json.Marshal(map[string]interface{}{
		"local_token_list": localTokenList,
	})
	path := "ilink/bot/get_bot_qrcode?bot_type=" + url.QueryEscape(botType)
	raw, err := c.doRequest(ctx, http.MethodPost, path, body, c.authHeaders(), APIDefaultTimeout)
	if err != nil {
		return nil, err
	}
	var out QRCodeResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return nil, err
	}
	return &out, nil
}

// QRStatusResponse 二维码状态轮询响应
type QRStatusResponse struct {
	Status       string `json:"status"`
	BotToken     string `json:"bot_token"`
	ILinkBotID   string `json:"ilink_bot_id"`
	ILinkUserID  string `json:"ilink_user_id"`
	BaseURL      string `json:"baseurl"`
	RedirectHost string `json:"redirect_host"`
}

// GetQRCodeStatus 长轮询二维码扫码状态
func (c *Client) GetQRCodeStatus(ctx context.Context, qrcode, verifyCode string) (*QRStatusResponse, error) {
	path := "ilink/bot/get_qrcode_status?qrcode=" + url.QueryEscape(qrcode)
	if verifyCode != "" {
		path += "&verify_code=" + url.QueryEscape(verifyCode)
	}
	raw, err := c.doRequest(ctx, http.MethodGet, path, nil, c.commonHeaders(), QRLongPollTimeout)
	if err != nil {
		if ctx.Err() != nil {
			return &QRStatusResponse{Status: "wait"}, nil
		}
		return &QRStatusResponse{Status: "wait"}, nil
	}
	var out QRStatusResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return nil, err
	}
	return &out, nil
}

// MessageItem 消息内容项
type MessageItem struct {
	Type     int `json:"type"`
	TextItem *struct {
		Text string `json:"text"`
	} `json:"text_item,omitempty"`
}

// WeixinMessage 入站消息
type WeixinMessage struct {
	FromUserID    string        `json:"from_user_id"`
	MessageType   int           `json:"message_type"`
	MessageState  int           `json:"message_state"`
	ItemList      []MessageItem `json:"item_list"`
	ContextToken  string        `json:"context_token"`
}

// GetUpdatesResponse 长轮询消息响应
type GetUpdatesResponse struct {
	Ret                 int             `json:"ret"`
	ErrCode             int             `json:"errcode"`
	ErrMsg              string          `json:"errmsg"`
	Msgs                []WeixinMessage `json:"msgs"`
	GetUpdatesBuf       string          `json:"get_updates_buf"`
	LongPollingTimeoutMs int            `json:"longpolling_timeout_ms"`
}

// GetUpdates 长轮询获取新消息
func (c *Client) GetUpdates(ctx context.Context, getUpdatesBuf string) (*GetUpdatesResponse, error) {
	body, _ := json.Marshal(map[string]interface{}{
		"get_updates_buf": getUpdatesBuf,
		"base_info":       c.buildBaseInfo(),
	})
	raw, err := c.doRequest(ctx, http.MethodPost, "ilink/bot/getupdates", body, c.authHeaders(), GetUpdatesTimeout)
	if err != nil {
		if ctx.Err() != nil {
			return &GetUpdatesResponse{Ret: 0, GetUpdatesBuf: getUpdatesBuf}, nil
		}
		return &GetUpdatesResponse{Ret: 0, GetUpdatesBuf: getUpdatesBuf}, nil
	}
	var out GetUpdatesResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return nil, err
	}
	return &out, nil
}

// SendTextMessage 发送文本回复
func (c *Client) SendTextMessage(ctx context.Context, toUserID, contextToken, text, clientID string) error {
	if clientID == "" {
		clientID = randomClientID()
	}
	payload := map[string]interface{}{
		"msg": map[string]interface{}{
			"to_user_id":    toUserID,
			"client_id":     clientID,
			"message_type":  2,
			"message_state": 2,
			"context_token": contextToken,
			"item_list": []map[string]interface{}{
				{"type": 1, "text_item": map[string]string{"text": text}},
			},
		},
		"base_info": c.buildBaseInfo(),
	}
	body, _ := json.Marshal(payload)
	_, err := c.doRequest(ctx, http.MethodPost, "ilink/bot/sendmessage", body, c.authHeaders(), APIDefaultTimeout)
	return err
}

func randomClientID() string {
	var b [8]byte
	_, _ = rand.Read(b[:])
	return fmt.Sprintf("%x", b)
}

func sanitizeBotAgent(raw string) string {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return DefaultBotAgent
	}
	if len(raw) > 256 {
		return raw[:256]
	}
	return raw
}

// ExtractText 从消息中提取首条文本
func ExtractText(msg WeixinMessage) string {
	for _, item := range msg.ItemList {
		if item.Type == 1 && item.TextItem != nil {
			return strings.TrimSpace(item.TextItem.Text)
		}
	}
	return ""
}
