// Package ayueverify 阿越云验证 - Go SDK
//
// 支持：卡密登录、心跳保活、公告获取、版本检查（含强制更新）、
//       卡密解绑、获取应用配置、获取外链文件、应用账号登录
//
// 环境：Go 1.16+，仅依赖标准库
//
// 使用示例：
//
//	c := ayueverify.New(ayueverify.Options{
//	    BaseURL: "https://yz.example.com",
//	    AppID:   10001,
//	    AppKey:  "your_app_key",
//	    Rc4Key:  "your_rc4_key",
//	    MiType:  0,
//	})
//	res := c.Login("KAMI-XXXX", "DEVICE-CODE")
//	if res.OK { fmt.Println("登录成功，到期:", res.ExpiresTs) }
package ayueverify

import (
	"crypto/md5"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math/rand"
	"os"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

// Options 客户端配置
type Options struct {
	BaseURL string      // 验证服务器地址，如 https://yz.example.com
	AppID   int         // 应用 ID
	AppKey  string      // 应用密钥
	Rc4Key  string      // 通信密钥
	MiType  int         // 加密类型 0=明文 1=RC4 2=Base64
	MiTime  int         // 时间差校验秒数（服务端 mi_time），>0 时必须传 t
	Sign    bool        // 是否签名
	Timeout time.Duration // 超时，默认 10s
}

// Client 验证客户端
type Client struct {
	opts     Options
	http     *http.Client
	lastErr  string
	lastCode int
	miTime   int
}

// Result 通用返回
type Result struct {
	OK        bool                   `json:"ok"`
	Code      int                    `json:"code"`
	Msg       interface{}            `json:"msg"`
	Data      map[string]interface{} `json:"data,omitempty"`
	ExpiresTs int64                  `json:"expires_ts,omitempty"`
}

// Config 应用配置
type Config struct {
	OK          bool
	Code        int
	Msg         string
	Version     string
	VersionInfo string
	UpdateURL   string
	UpdateShow  string
	MustUpdate  bool
}

// UpdateInfo 更新信息
type UpdateInfo struct {
	OK         bool
	HasUpdate  bool
	MustUpdate bool
	Latest     string
	URL        string
	Desc       string
	Msg        string
}

// New 创建客户端
func New(opts Options) *Client {
	if opts.Timeout <= 0 {
		opts.Timeout = 10 * time.Second
	}
	opts.BaseURL = strings.TrimRight(opts.BaseURL, "/") + "/"
	if opts.MiTime == 0 {
		opts.MiTime = 10
	}
	return &Client{
		opts: opts,
		http: &http.Client{Timeout: opts.Timeout},
	}
}

// LastError 最近错误
func (c *Client) LastError() string { return c.lastErr }

// LastCode 最近状态码
func (c *Client) LastCode() int { return c.lastCode }

// ==================== 业务接口 ====================

// Login 卡密登录
func (c *Client) Login(kami, markcode string) *Result {
	r := c.request("kmlogon", map[string]string{
		"kami":     kami,
		"markcode": markcode,
	})
	return c.normalize(r, true)
}

// Heartbeat 心跳保活
func (c *Client) Heartbeat(kami, markcode string) *Result {
	r := c.request("heartbeat", map[string]string{
		"kami":     kami,
		"markcode": markcode,
	})
	return c.normalize(r, true)
}

// HeartbeatQuit 退出心跳
func (c *Client) HeartbeatQuit(kami string) *Result {
	return c.Heartbeat(kami, "quit")
}

// Notice 获取公告
func (c *Client) Notice() string {
	r := c.request("notice", map[string]string{})
	if codeOf(r) == 200 {
		if m, ok := r["msg"].(map[string]interface{}); ok {
			if v, ok := m["app_gg"].(string); ok {
				return v
			}
		}
	}
	return ""
}

// GetConfig 获取应用配置
func (c *Client) GetConfig(withPay bool) *Config {
	p := map[string]string{}
	if withPay {
		p["pay"] = "1"
	}
	r := c.request("ini", p)
	cfg := &Config{Code: c.lastCode}

	if codeOf(r) == 200 {
		cfg.OK = true
		if m, ok := r["msg"].(map[string]interface{}); ok {
			cfg.Version = strOf(m, "version")
			cfg.VersionInfo = strOf(m, "version_info")
			cfg.UpdateURL = strOf(m, "app_update_url")
			cfg.UpdateShow = strOf(m, "app_update_show")
			cfg.MustUpdate = strOf(m, "app_update_must") == "y"
		}
	} else {
		cfg.Msg = c.lastErr
	}
	return cfg
}

// CheckUpdate 检查更新
func (c *Client) CheckUpdate(currentVersion string) *UpdateInfo {
	cfg := c.GetConfig(false)
	u := &UpdateInfo{}
	if !cfg.OK {
		u.Msg = cfg.Msg
		return u
	}
	u.OK = true
	u.HasUpdate = VersionCompare(cfg.Version, currentVersion) > 0
	u.MustUpdate = u.HasUpdate && cfg.MustUpdate
	u.Latest = cfg.Version
	u.URL = cfg.UpdateURL
	if cfg.UpdateShow != "" {
		u.Desc = cfg.UpdateShow
	} else {
		u.Desc = cfg.VersionInfo
	}
	return u
}

// Unbind 卡密解绑
func (c *Client) Unbind(kami, markcode string) *Result {
	r := c.request("kmunmachine", map[string]string{
		"kami":     kami,
		"markcode": markcode,
	})
	return c.normalize(r, false)
}

// GetFilesRaw 获取外链文件原始 JSON
func (c *Client) GetFilesRaw(fileID int) (string, error) {
	p := map[string]string{}
	if fileID > 0 {
		p["id"] = strconv.Itoa(fileID)
	}
	return c.requestRaw("getfile", p)
}

// UserLogin 应用账号登录
func (c *Client) UserLogin(user, password string) *Result {
	r := c.request("userlogon", map[string]string{
		"user":     user,
		"password": password,
	})
	return c.normalize(r, false)
}

// UserRegister 应用账号注册
func (c *Client) UserRegister(user, password string) *Result {
	r := c.request("userreg", map[string]string{
		"user":     user,
		"password": password,
	})
	return c.normalize(r, false)
}

// UserHeartbeat 应用账号心跳
func (c *Client) UserHeartbeat(user, markcode string) *Result {
	r := c.request("userheartbeat", map[string]string{
		"user":     user,
		"markcode": markcode,
	})
	return c.normalize(r, false)
}

// ==================== 底层通信 ====================

func (c *Client) requestRaw(api string, fields map[string]string) (string, error) {
	c.lastErr = ""
	c.lastCode = 0

	value := randomStr(16)
	f := make(map[string]string, len(fields)+1)
	for k, v := range fields {
		f[k] = v
	}
	f["value"] = value

	// 关键：服务端 mi_time > 0 时强制要求 t 参数，缺失返回 108
	if c.opts.MiTime > 0 {
		if _, ok := f["t"]; !ok {
			f["t"] = strconv.FormatInt(time.Now().Unix(), 10)
		}
	}

	endpoint := c.opts.BaseURL + "api.php?api=" + url.QueryEscape(api) +
		"&app=" + strconv.Itoa(c.opts.AppID)

	post := map[string]string{}
	if c.opts.MiType == 0 {
		for k, v := range f {
			post[k] = v
		}
	} else {
		post["data"] = c.encrypt(buildQuery(f))
		post["value"] = value
	}

	if c.opts.Sign {
		src := make(map[string]string, len(f)+1)
		for k, v := range f {
			src[k] = v
		}
		if c.opts.MiType != 0 {
			src["data"] = post["data"]
		}
		post["sign"] = c.makeSign(src)
	}

	form := url.Values{}
	for k, v := range post {
		form.Set(k, v)
	}

	req, err := http.NewRequest("POST", endpoint, strings.NewReader(form.Encode()))
	if err != nil {
		c.lastErr = "构造请求失败: " + err.Error()
		c.lastCode = -1
		return "", err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("User-Agent", "AyueVerify-Go-SDK/1.0.0")

	resp, err := c.http.Do(req)
	if err != nil {
		c.lastErr = "网络请求失败: " + err.Error()
		c.lastCode = -1
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		c.lastErr = "读取响应失败: " + err.Error()
		c.lastCode = -1
		return "", err
	}
	return string(body), nil
}

func (c *Client) request(api string, fields map[string]string) map[string]interface{} {
	raw, err := c.requestRaw(api, fields)
	if err != nil {
		return map[string]interface{}{"code": -1, "msg": c.lastErr}
	}

	var obj map[string]interface{}
	// 去除 UTF-8 BOM
	raw = strings.TrimSpace(strings.TrimPrefix(raw, "\ufeff"))
	if err := json.Unmarshal([]byte(raw), &obj); err != nil {
		c.lastErr = "返回格式错误: " + truncate(raw, 120)
		c.lastCode = -1
		return map[string]interface{}{"code": -1, "msg": c.lastErr}
	}

	c.lastCode = codeOf(obj)
	if c.lastCode != 200 {
		if m, ok := obj["msg"]; ok {
			c.lastErr = fmt.Sprint(m)
		}
	}
	return obj
}

func (c *Client) normalize(r map[string]interface{}, wantExpires bool) *Result {
	res := &Result{
		Code: codeOf(r),
		Msg:  r["msg"],
	}
	res.OK = res.Code == 200

	if m, ok := r["msg"].(map[string]interface{}); ok {
		res.Data = m
		if wantExpires && res.OK {
			switch v := m["expires_ts"].(type) {
			case float64:
				res.ExpiresTs = int64(v)
			case string:
				n, _ := strconv.ParseInt(v, 10, 64)
				res.ExpiresTs = n
			}
		}
	}
	return res
}

func (c *Client) makeSign(fields map[string]string) string {
	ignore := map[string]bool{
		"sign": true, "app": true, "api": true, "value": true,
		"PHPSESSID": true, "sec_defend": true, "sidenav-state": true,
	}
	var sb strings.Builder
	for k, v := range fields {
		if ignore[k] {
			continue
		}
		sb.WriteString(k + "=" + v + "&")
	}
	sb.WriteString(c.opts.AppKey)
	sum := md5.Sum([]byte(sb.String()))
	return hex.EncodeToString(sum[:])
}

func (c *Client) encrypt(plain string) string {
	if c.opts.MiType == 1 {
		// 关键：服务端 mi_rc4 会先做 UTF-8 -> GBK 转换，客户端必须保持一致，
		// 否则含中文的参数会解密失败。
		return rc4Hex(utf8ToGbk(plain), utf8ToGbk(c.opts.Rc4Key))
	}
	return base64.StdEncoding.EncodeToString([]byte(plain))
}

// utf8ToGbk 将 UTF-8 字符串转为 GBK 字节串（以 string 承载字节）
// 未引入 golang.org/x/text 时，对纯 ASCII 直接返回；含中文时尽量转换。
func utf8ToGbk(s string) string {
	asciiOnly := true
	for i := 0; i < len(s); i++ {
		if s[i] >= 0x80 {
			asciiOnly = false
			break
		}
	}
	if asciiOnly {
		return s
	}
	// 使用 x/text 时解锁完整转换：
	//   import "golang.org/x/text/encoding/simplifiedchinese"
	//   enc := simplifiedchinese.GBK.NewEncoder()
	//   b, err := enc.String(s)
	//   if err == nil { return b }
	// 无该依赖时退回原串（中文可能乱码，建议参数避免中文）
	return s
}

// ==================== 工具函数 ====================

func codeOf(r map[string]interface{}) int {
	v, ok := r["code"]
	if !ok || v == nil {
		return -1
	}
	switch n := v.(type) {
	case float64:
		return int(n)
	case string:
		i, _ := strconv.Atoi(n)
		return i
	}
	return -1
}

func strOf(m map[string]interface{}, key string) string {
	if v, ok := m[key]; ok && v != nil {
		return fmt.Sprint(v)
	}
	return ""
}

func buildQuery(f map[string]string) string {
	parts := make([]string, 0, len(f))
	for k, v := range f {
		parts = append(parts, k+"="+v)
	}
	return strings.Join(parts, "&")
}

func rc4Hex(data, key string) string {
	if key == "" {
		return ""
	}
	s := make([]int, 256)
	for i := range s {
		s[i] = i
	}
	j := 0
	kl := len(key)
	for i := 0; i < 256; i++ {
		j = (j + s[i] + int(key[i%kl])) % 256
		s[i], s[j] = s[j], s[i]
	}
	x, y := 0, 0
	var sb strings.Builder
	for n := 0; n < len(data); n++ {
		x = (x + 1) % 256
		y = (y + s[x]) % 256
		s[x], s[y] = s[y], s[x]
		sb.WriteString(fmt.Sprintf("%02x", byte(data[n])^byte(s[(s[x]+s[y])%256])))
	}
	return sb.String()
}

func randomStr(n int) string {
	const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	rand.Seed(time.Now().UnixNano())
	b := make([]byte, n)
	for i := range b {
		b[i] = chars[rand.Intn(len(chars))]
	}
	return string(b)
}

func truncate(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n]
}

// VersionCompare 版本比较：v1 > v2 返回 1
func VersionCompare(v1, v2 string) int {
	a := splitVersion(v1)
	b := splitVersion(v2)
	n := len(a)
	if len(b) > n {
		n = len(b)
	}
	for i := 0; i < n; i++ {
		x, y := "0", "0"
		if i < len(a) {
			x = a[i]
		}
		if i < len(b) {
			y = b[i]
		}
		xi, err1 := strconv.Atoi(x)
		yi, err2 := strconv.Atoi(y)
		if err1 == nil && err2 == nil {
			if xi > yi {
				return 1
			}
			if xi < yi {
				return -1
			}
		} else {
			if x > y {
				return 1
			}
			if x < y {
				return -1
			}
		}
	}
	return 0
}

func splitVersion(v string) []string {
	v = strings.TrimSpace(v)
	repl := strings.NewReplacer("-", ".", "+", ".")
	return strings.Split(repl.Replace(v), ".")
}

// GenerateMarkcode 生成设备码（首次运行应持久化）
func GenerateMarkcode() string {
	seed := fmt.Sprintf("%s|%d|%d", hostname(), time.Now().UnixNano(), rand.Int63())
	sum := md5.Sum([]byte(seed))
	return strings.ToUpper(hex.EncodeToString(sum[:])[:24])
}

func hostname() string {
	h, err := os.Hostname()
	if err != nil {
		return "unknown"
	}
	return h
}

// ErrNotInitialized 未初始化
var ErrNotInitialized = errors.New("ayueverify: client not initialized")
