package main

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

// doRequest 发送 HTTP 请求并返回响应内容
// 参数: method 请求方法, urlStr 请求地址, headers 请求头, body 请求体字节
// 返回: 响应字节与错误
func doRequest(method, urlStr string, headers map[string]string, body []byte) ([]byte, int, error) {
	req, err := http.NewRequest(method, urlStr, bytes.NewReader(body))
	if err != nil {
		return nil, 0, err
	}
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, 0, err
	}
	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, resp.StatusCode, err
	}
	return data, resp.StatusCode, nil
}

// md5Hex 计算字符串的 MD5 十六进制
// 参数: s 输入字符串
// 返回: 32位十六进制 MD5 字符串
func md5Hex(s string) string {
	h := md5.Sum([]byte(s))
	return hex.EncodeToString(h[:])
}

// Failure 统一错误结构
type Failure struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

// AdminLoginResponse 管理端登录返回
type AdminLoginResponse struct {
	Token   string `json:"token"`
	IsSuper int32  `json:"is_super"`
}

// SimpleMessage 简单消息返回
type SimpleMessage struct {
	Message string `json:"message"`
}

// AppActivityItem APP活动项
type AppActivityItem struct {
	ID                 int64  `json:"id"`
	Name               string `json:"name"`
	Banner             string `json:"banner"`
	ActivityCategoryID int64  `json:"activity_category_id"`
	Status             int32  `json:"status"`
	PriceDraw          int64  `json:"price_draw"`
	IsBoss             int32  `json:"is_boss"`
}

// AppListActivitiesResponse APP活动列表返回
type AppListActivitiesResponse struct {
	Page     int               `json:"page"`
	PageSize int               `json:"page_size"`
	Total    int64             `json:"total"`
	List     []AppActivityItem `json:"list"`
}

// AdminIssueData 管理端期数项
type AdminIssueData struct {
	ID          int64  `json:"id"`
	IssueNumber string `json:"issue_number"`
	Status      int32  `json:"status"`
	Sort        int32  `json:"sort"`
}

// AdminListIssuesResponse 管理端期数列表返回
type AdminListIssuesResponse struct {
	Page     int              `json:"page"`
	PageSize int              `json:"page_size"`
	Total    int64            `json:"total"`
	List     []AdminIssueData `json:"list"`
}

// RewardItem 奖励项
type RewardItem struct {
	ProductID   int64  `json:"product_id"`
	Name        string `json:"name"`
	Weight      int32  `json:"weight"`
	Quantity    int64  `json:"quantity"`
	OriginalQty int64  `json:"original_qty"`
	Level       int32  `json:"level"`
	Sort        int32  `json:"sort"`
	IsBoss      int32  `json:"is_boss"`
}

// ListRewardsResponse 奖励列表返回
type ListRewardsResponse struct {
	List []RewardItem `json:"list"`
}

// loginAdmin 管理端登录获取 Token
// 参数: baseURL 服务基地址, username 用户名, passwordMD5 MD5密码
// 返回: token 字符串与错误
func loginAdmin(baseURL, username, password string) (string, error) {
	payload := map[string]string{"username": username, "password": password}
	b, _ := json.Marshal(payload)
	data, code, err := doRequest("POST", baseURL+"/api/admin/login", map[string]string{"Content-Type": "application/json"}, b)
	if err != nil {
		return "", err
	}
	if code != http.StatusOK {
		var fail Failure
		_ = json.Unmarshal(data, &fail)
		return "", fmt.Errorf("login failed: %d %s", code, fail.Message)
	}
	var res AdminLoginResponse
	if err := json.Unmarshal(data, &res); err != nil {
		return "", err
	}
	return res.Token, nil
}

// createActivity 创建活动
// 参数: baseURL 基地址, token 管理端token, name 名称, categoryID 分类ID, isBoss 是否Boss
// 返回: 错误
func createActivity(baseURL, token, name string, categoryID int64, isBoss int32) error {
	start := time.Now().UTC().Format(time.RFC3339)
	end := time.Now().Add(24 * time.Hour).UTC().Format(time.RFC3339)
	payload := map[string]any{
		"name":                 name,
		"banner":               "",
		"activity_category_id": categoryID,
		"status":               1,
		"price_draw":           0,
		"is_boss":              isBoss,
		"start_time":           start,
		"end_time":             end,
	}
	b, _ := json.Marshal(payload)
	headers := map[string]string{"Content-Type": "application/json", "Authorization": token}
	data, code, err := doRequest("POST", baseURL+"/api/admin/activities", headers, b)
	if err != nil {
		return err
	}
	if code != http.StatusOK {
		var fail Failure
		_ = json.Unmarshal(data, &fail)
		return fmt.Errorf("create activity failed: %d %s", code, fail.Message)
	}
	return nil
}

// createGuild 创建工会
// 参数: baseURL 基地址, token 管理端token, name 名称, ownerID 会长用户ID, isOpen 是否公开
// 返回: 错误
func createGuild(baseURL, token, name string, ownerID int64, isOpen int32) error {
	payload := map[string]any{
		"name":          name,
		"owner_id":      ownerID,
		"description":   "",
		"join_mode":     2,
		"consume_limit": 0,
		"avatar_url":    "",
		"is_open":       isOpen,
	}
	b, _ := json.Marshal(payload)
	headers := map[string]string{"Content-Type": "application/json", "Authorization": token}
	data, code, err := doRequest("POST", baseURL+"/api/admin/guilds", headers, b)
	if err != nil {
		return err
	}
	if code != http.StatusOK {
		var fail Failure
		_ = json.Unmarshal(data, &fail)
		return fmt.Errorf("create guild failed: %d %s", code, fail.Message)
	}
	return nil
}

// findGuildIDByName 通过名称查询工会ID
// 参数: baseURL 基地址, name 名称
// 返回: 工会ID与错误
func findGuildIDByName(baseURL, name string) (int64, error) {
	q := url.Values{}
	q.Set("name", name)
	q.Set("page", "1")
	q.Set("page_size", "20")
	data, code, err := doRequest("GET", baseURL+"/api/app/guilds?"+q.Encode(), nil, nil)
	if err != nil {
		return 0, err
	}
	if code != http.StatusOK {
		var fail Failure
		_ = json.Unmarshal(data, &fail)
		return 0, fmt.Errorf("list guilds failed: %d %s", code, fail.Message)
	}
	type GuildItem struct {
		ID   int64
		Name string
	}
	type ListGuildsResponse struct{ List []GuildItem }
	var res ListGuildsResponse
	if err := json.Unmarshal(data, &res); err != nil {
		return 0, err
	}
	for _, it := range res.List {
		if strings.EqualFold(it.Name, name) {
			return it.ID, nil
		}
	}
	return 0, fmt.Errorf("guild not found: %s", name)
}

// joinGuildApp APP 加入工会
// 参数: baseURL 基地址, guildID 工会ID, userID 用户ID
// 返回: 错误
func joinGuildApp(baseURL string, guildID, userID int64) error {
	b, _ := json.Marshal(map[string]any{"user_id": userID})
	u := fmt.Sprintf("%s/api/app/guilds/%d/members", baseURL, guildID)
	data, code, err := doRequest("POST", u, map[string]string{"Content-Type": "application/json"}, b)
	if err != nil {
		return err
	}
	if code != http.StatusOK {
		var fail Failure
		_ = json.Unmarshal(data, &fail)
		return fmt.Errorf("join guild failed: %d %s", code, fail.Message)
	}
	return nil
}

// listGuildMembersApp APP 工会成员列表
// 参数: baseURL 基地址, guildID 工会ID
// 返回: 成员数量与错误
func listGuildMembersApp(baseURL string, guildID int64) (int, error) {
	q := url.Values{}
	q.Set("page", "1")
	q.Set("page_size", "100")
	u := fmt.Sprintf("%s/api/app/guilds/%d/members?%s", baseURL, guildID, q.Encode())
	data, code, err := doRequest("GET", u, nil, nil)
	if err != nil {
		return 0, err
	}
	if code != http.StatusOK {
		var fail Failure
		_ = json.Unmarshal(data, &fail)
		return 0, fmt.Errorf("list members failed: %d %s", code, fail.Message)
	}
	type MemberItem struct{ ID int64 }
	type ListResponse struct{ List []MemberItem }
	var res ListResponse
	if err := json.Unmarshal(data, &res); err != nil {
		return 0, err
	}
	return len(res.List), nil
}

// leaveGuildApp APP 离开工会
// 参数: baseURL 基地址, guildID 工会ID, userID 用户ID
// 返回: 错误
func leaveGuildApp(baseURL string, guildID, userID int64) error {
	u := fmt.Sprintf("%s/api/app/guilds/%d/members/%d", baseURL, guildID, userID)
	data, code, err := doRequest("DELETE", u, nil, nil)
	if err != nil {
		return err
	}
	if code != http.StatusOK {
		var fail Failure
		_ = json.Unmarshal(data, &fail)
		return fmt.Errorf("leave guild failed: %d %s", code, fail.Message)
	}
	return nil
}

// findActivityIDByName 通过名称查询活动ID
// 参数: baseURL 基地址, name 名称
// 返回: 活动ID与错误
func findActivityIDByName(baseURL, name string) (int64, error) {
	q := url.Values{}
	q.Set("name", name)
	q.Set("page", "1")
	q.Set("page_size", "20")
	data, code, err := doRequest("GET", baseURL+"/api/app/activities?"+q.Encode(), nil, nil)
	if err != nil {
		return 0, err
	}
	if code != http.StatusOK {
		var fail Failure
		_ = json.Unmarshal(data, &fail)
		return 0, fmt.Errorf("list activities failed: %d %s", code, fail.Message)
	}
	var res AppListActivitiesResponse
	if err := json.Unmarshal(data, &res); err != nil {
		return 0, err
	}
	for _, it := range res.List {
		if strings.EqualFold(it.Name, name) {
			return it.ID, nil
		}
	}
	return 0, fmt.Errorf("activity not found: %s", name)
}

// createIssue 创建活动期数
// 参数: baseURL 基地址, token 管理端token, activityID 活动ID, issueNumber 期号
// 返回: 错误
func createIssue(baseURL, token string, activityID int64, issueNumber string) error {
	payload := map[string]any{"issue_number": issueNumber, "status": 1, "sort": 1}
	b, _ := json.Marshal(payload)
	headers := map[string]string{"Content-Type": "application/json", "Authorization": token}
	u := fmt.Sprintf("%s/api/admin/activities/%d/issues", baseURL, activityID)
	data, code, err := doRequest("POST", u, headers, b)
	if err != nil {
		return err
	}
	if code != http.StatusOK {
		var fail Failure
		_ = json.Unmarshal(data, &fail)
		return fmt.Errorf("create issue failed: %d %s", code, fail.Message)
	}
	return nil
}

// findIssueIDByNumber 通过期号查询期ID
// 参数: baseURL 基地址, token 管理端token, activityID 活动ID, issueNumber 期号
// 返回: 期ID与错误
func findIssueIDByNumber(baseURL, token string, activityID int64, issueNumber string) (int64, error) {
	q := url.Values{}
	q.Set("page", "1")
	q.Set("page_size", "20")
	headers := map[string]string{"Authorization": token}
	u := fmt.Sprintf("%s/api/admin/activities/%d/issues?%s", baseURL, activityID, q.Encode())
	data, code, err := doRequest("GET", u, headers, nil)
	if err != nil {
		return 0, err
	}
	if code != http.StatusOK {
		var fail Failure
		_ = json.Unmarshal(data, &fail)
		return 0, fmt.Errorf("list admin issues failed: %d %s", code, fail.Message)
	}
	var res AdminListIssuesResponse
	if err := json.Unmarshal(data, &res); err != nil {
		return 0, err
	}
	for _, it := range res.List {
		if strings.EqualFold(it.IssueNumber, issueNumber) {
			return it.ID, nil
		}
	}
	return 0, fmt.Errorf("issue not found: %s", issueNumber)
}

// createRewards 批量创建奖励
// 参数: baseURL 基地址, token 管理端token, activityID 活动ID, issueID 期ID, rewards 奖励列表
// 返回: 错误
func createRewards(baseURL, token string, activityID, issueID int64, rewards []RewardItem) error {
	payload := map[string]any{"rewards": rewards}
	b, _ := json.Marshal(payload)
	headers := map[string]string{"Content-Type": "application/json", "Authorization": token}
	u := fmt.Sprintf("%s/api/admin/activities/%d/issues/%d/rewards", baseURL, activityID, issueID)
	data, code, err := doRequest("POST", u, headers, b)
	if err != nil {
		return err
	}
	if code != http.StatusOK {
		var fail Failure
		_ = json.Unmarshal(data, &fail)
		return fmt.Errorf("create rewards failed: %d %s", code, fail.Message)
	}
	return nil
}

// verifyAppChain 验证 APP 端链路
// 参数: baseURL 基地址, activityID 活动ID, issueID 期ID
// 返回: 错误
func verifyAppChain(baseURL string, activityID, issueID int64) error {
	_, code1, err := doRequest("GET", fmt.Sprintf("%s/api/app/activities/%d", baseURL, activityID), nil, nil)
	if err != nil || code1 != http.StatusOK {
		return fmt.Errorf("app activity detail failed")
	}
	q := url.Values{}
	q.Set("page", "1")
	q.Set("page_size", "20")
	_, code2, err := doRequest("GET", fmt.Sprintf("%s/api/app/activities/%d/issues?%s", baseURL, activityID, q.Encode()), nil, nil)
	if err != nil || code2 != http.StatusOK {
		return fmt.Errorf("app issues list failed")
	}
	_, code3, err := doRequest("GET", fmt.Sprintf("%s/api/app/activities/%d/issues/%d/rewards", baseURL, activityID, issueID), nil, nil)
	if err != nil || code3 != http.StatusOK {
		return fmt.Errorf("app rewards list failed")
	}
	q2 := url.Values{}
	q2.Set("page", "1")
	q2.Set("page_size", "20")
	_, code4, err := doRequest("GET", fmt.Sprintf("%s/api/app/activities/%d/issues/%d/draw_logs?%s", baseURL, activityID, issueID, q2.Encode()), nil, nil)
	if err != nil || code4 != http.StatusOK {
		return fmt.Errorf("app draw logs list failed")
	}
	return nil
}

// main 主流程入口
// 参数: 命令行参数
// 返回: 无
func main() {
	base := flag.String("base", "http://127.0.0.1:9991", "api base url")
	chain := flag.String("chain", "activity", "test chain: activity|guild")
	adminUser := flag.String("admin_user", os.Getenv("ADMIN_USER"), "admin username")
	adminPass := flag.String("admin_pass", os.Getenv("ADMIN_PASS"), "admin password (plain)")
	adminPassMD5 := flag.String("admin_pass_md5", os.Getenv("ADMIN_PASS_MD5"), "admin password (alternate input)")
	activityName := flag.String("activity_name", fmt.Sprintf("自动化活动-%d", time.Now().Unix()), "activity name")
	issueNumber := flag.String("issue_number", fmt.Sprintf("ISSUE-%d", time.Now().Unix()), "issue number")
	categoryID := flag.Int64("category_id", 1, "activity category id")
	isBoss := flag.Int("is_boss", 0, "activity boss flag 0/1")
	guildName := flag.String("guild_name", fmt.Sprintf("自动化工会-%d", time.Now().Unix()), "guild name")
	ownerID := flag.Int64("owner_id", 1001, "guild owner user id")
	memberID := flag.Int64("member_id", 2002, "guild member user id")
	isOpen := flag.Int("is_open", 1, "guild open flag 1/2")
	flag.Parse()

	if *adminUser == "" || (*adminPass == "" && *adminPassMD5 == "") {
		fmt.Println("缺少管理员登录参数")
		os.Exit(1)
	}

	password := *adminPassMD5
	if password == "" {
		password = md5Hex(*adminPass)
	}

	token, err := loginAdmin(*base, *adminUser, password)
	if err != nil {
		fmt.Println("登录失败", err)
		os.Exit(1)
	}
	fmt.Println("登录成功")

	if *chain == "activity" {
		if err := createActivity(*base, token, *activityName, *categoryID, int32(*isBoss)); err != nil {
			fmt.Println("创建活动失败", err)
			os.Exit(1)
		}
		fmt.Println("创建活动成功")
		actID, err := findActivityIDByName(*base, *activityName)
		if err != nil {
			fmt.Println("查询活动ID失败", err)
			os.Exit(1)
		}
		fmt.Println("活动ID", actID)
		if err := createIssue(*base, token, actID, *issueNumber); err != nil {
			fmt.Println("创建期数失败", err)
			os.Exit(1)
		}
		fmt.Println("创建期数成功")
		issueID, err := findIssueIDByNumber(*base, token, actID, *issueNumber)
		if err != nil {
			fmt.Println("查询期ID失败", err)
			os.Exit(1)
		}
		fmt.Println("期ID", issueID)
		rewards := []RewardItem{
			{ProductID: 0, Name: "S-稀有奖", Weight: 1, Quantity: 1, OriginalQty: 1, Level: 1, Sort: 1, IsBoss: 0},
			{ProductID: 0, Name: "A-普通奖", Weight: 10, Quantity: 10, OriginalQty: 10, Level: 2, Sort: 2, IsBoss: 0},
		}
		if err := createRewards(*base, token, actID, issueID, rewards); err != nil {
			fmt.Println("创建奖励失败", err)
			os.Exit(1)
		}
		fmt.Println("创建奖励成功")
		if err := verifyAppChain(*base, actID, issueID); err != nil {
			fmt.Println("APP链路验证失败", err)
			os.Exit(1)
		}
		fmt.Println("APP链路验证成功")
		return
	}

	if *chain == "guild" {
		if err := createGuild(*base, token, *guildName, *ownerID, int32(*isOpen)); err != nil {
			fmt.Println("创建工会失败", err)
			os.Exit(1)
		}
		fmt.Println("创建工会成功")
		gid, err := findGuildIDByName(*base, *guildName)
		if err != nil {
			fmt.Println("查询工会ID失败", err)
			os.Exit(1)
		}
		fmt.Println("工会ID", gid)
		if err := joinGuildApp(*base, gid, *memberID); err != nil {
			if strings.Contains(err.Error(), "join limited") {
				alt := time.Now().Unix() % 100000
				*memberID = 300000 + alt
				if err2 := joinGuildApp(*base, gid, *memberID); err2 != nil {
					fmt.Println("加入工会失败", err2)
					os.Exit(1)
				}
				fmt.Println("加入工会成功(使用备用用户)")
			} else {
				fmt.Println("加入工会失败", err)
				os.Exit(1)
			}
		} else {
			fmt.Println("加入工会成功")
		}
		count, err := listGuildMembersApp(*base, gid)
		if err != nil {
			fmt.Println("查看成员失败", err)
			os.Exit(1)
		}
		fmt.Println("成员数量", count)
		if err := leaveGuildApp(*base, gid, *memberID); err != nil {
			fmt.Println("离开工会失败", err)
			os.Exit(1)
		}
		fmt.Println("离开工会成功")
		err = joinGuildApp(*base, gid, *memberID)
		if err == nil {
			fmt.Println("预期被限制加入但成功了")
			os.Exit(1)
		}
		if err != nil && strings.Contains(err.Error(), "join limited") {
			fmt.Println("24小时限制验证成功")
		} else {
			fmt.Println("24小时限制验证失败", err)
			os.Exit(1)
		}
		return
	}
}
