package main

import (
	"bytes"
	"context"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"
)

const (
	defaultBaseURL  = "https://cmp-api.xm-iot.cn"
	defaultAccount  = "15065083933"
	defaultPassword = "adm@3933"
)

// apiResponse 是文档定义的统一响应外壳，data 保持原始 JSON 便于兼容列表或分页结构。
type apiResponse struct {
	Code      int             `json:"code"`
	Data      json.RawMessage `json:"data"`
	Msg       string          `json:"msg"`
	Timestamp string          `json:"timestamp"`
}

// walletPackageOrderRequest 是“钱包套餐购买”的请求体。
type walletPackageOrderRequest struct {
	CardNos     []string `json:"card_nos"`
	PackageCode string   `json:"package_code"`
}

// cardResumeRequest 是"机卡分离卡复机"的请求体。
type cardResumeRequest struct {
	CardNo string `json:"card_no"`
}

// deviceSwitchCardRequest 是"设备切网"的请求体。
type deviceSwitchCardRequest struct {
	DeviceNo string `json:"device_no"`
	ICCID    string `json:"iccid"`
}

// deviceOperationRequest 是设备操作（重启/恢复出厂）的请求体。
type deviceOperationRequest struct {
	DeviceNo string `json:"device_no"`
}

// client 是第三方调用方视角的独立客户端，不依赖当前项目内部代码。
type client struct {
	baseURL    string
	account    string
	password   string
	httpClient *http.Client
}

func main() {
	baseURL := flag.String("base-url", envOrDefault("AGENT_OPEN_API_BASE_URL", defaultBaseURL), "接口服务地址")
	account := flag.String("account", envOrDefault("AGENT_OPEN_API_ACCOUNT", defaultAccount), "代理账号用户名或手机号")
	password := flag.String("password", envOrDefault("AGENT_OPEN_API_PASSWORD", defaultPassword), "代理账号登录密码")
	action := flag.String("action", "wallet-balance", "调用动作：realname-status、card-status、card-traffic、card-resume、packages、wallet-balance、package-order、wallet-transactions、device-traffic、device-switch-card、device-reboot、device-reset")
	cardNo := flag.String("card-no", "", "卡标识，支持 ICCID、虚拟号、MSISDN")
	deviceNo := flag.String("device-no", "", "设备标识，支持虚拟号、IMEI")
	iccid := flag.String("iccid", "", "目标卡 ICCID，device-switch-card 时使用")
	cardNos := flag.String("card-nos", "", "卡标识列表，多个值用英文逗号分隔")
	packageCode := flag.String("package-code", "", "套餐编码")
	page := flag.Int("page", 0, "页码，不传或 0 表示省略")
	pageSize := flag.Int("page-size", 0, "每页数量，不传或 0 表示省略")
	packageType := flag.String("package-type", "", "套餐类型：formal 或 addon")
	seriesID := flag.Int("series-id", -1, "套餐系列 ID，不传或 -1 表示省略")
	packageName := flag.String("package-name", "", "套餐名称，模糊搜索")
	transactionType := flag.String("transaction-type", "", "交易类型：recharge、deduct、refund")
	startDate := flag.String("start-date", "", "开始日期，格式 YYYY-MM-DD")
	endDate := flag.String("end-date", "", "结束日期，格式 YYYY-MM-DD")
	timeout := flag.Duration("timeout", 15*time.Second, "请求超时时间")
	flag.Parse()

	c := &client{
		baseURL:  strings.TrimRight(*baseURL, "/"),
		account:  *account,
		password: *password,
		httpClient: &http.Client{
			Timeout: *timeout,
		},
	}

	ctx := context.Background()
	statusCode, responseBody, err := dispatch(ctx, c, runOptions{
		action:          *action,
		cardNo:          *cardNo,
		cardNos:         *cardNos,
		packageCode:     *packageCode,
		page:            *page,
		pageSize:        *pageSize,
		packageType:     *packageType,
		seriesID:        *seriesID,
		packageName:     *packageName,
		transactionType: *transactionType,
		startDate:       *startDate,
		endDate:         *endDate,
		deviceNo:        *deviceNo,
		iccid:           *iccid,
	})
	if err != nil {
		fmt.Fprintf(os.Stderr, "调用失败：%v\n", err)
		os.Exit(1)
	}

	fmt.Printf("HTTP %d\n", statusCode)
	printJSON(responseBody)
	if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
		os.Exit(1)
	}
}

// runOptions 保存命令行参数，避免把主流程写成过长函数。
type runOptions struct {
	action          string
	cardNo          string
	cardNos         string
	packageCode     string
	page            int
	pageSize        int
	packageType     string
	seriesID        int
	packageName     string
	transactionType string
	startDate       string
	endDate         string
	deviceNo        string
	iccid           string
}

func dispatch(ctx context.Context, c *client, opts runOptions) (int, []byte, error) {
	switch opts.action {
	case "realname-status":
		if opts.cardNo == "" {
			return 0, nil, errors.New("realname-status 需要传入 -card-no")
		}
		return c.queryRealnameStatus(ctx, opts.cardNo)
	case "card-status":
		if opts.cardNo == "" {
			return 0, nil, errors.New("card-status 需要传入 -card-no")
		}
		return c.queryCardStatus(ctx, opts.cardNo)
	case "card-traffic":
		if opts.cardNo == "" {
			return 0, nil, errors.New("card-traffic 需要传入 -card-no")
		}
		return c.queryCardTraffic(ctx, opts.cardNo)
	case "card-resume":
		if opts.cardNo == "" {
			return 0, nil, errors.New("card-resume 需要传入 -card-no")
		}
		return c.resumeCard(ctx, opts.cardNo)
	case "packages":
		return c.queryPackages(ctx, packageQuery(opts))
	case "wallet-balance":
		return c.queryWalletBalance(ctx)
	case "package-order":
		requestBody, err := buildPackageOrderRequest(opts.cardNos, opts.packageCode)
		if err != nil {
			return 0, nil, err
		}
		return c.createWalletPackageOrder(ctx, requestBody)
	case "wallet-transactions":
		if err := validateDateRange(opts.startDate, opts.endDate); err != nil {
			return 0, nil, err
		}
		return c.queryWalletTransactions(ctx, walletTransactionQuery(opts))
	case "device-traffic":
		if opts.deviceNo == "" {
			return 0, nil, errors.New("device-traffic 需要传入 -device-no")
		}
		return c.queryDeviceTraffic(ctx, opts.deviceNo)
	case "device-switch-card":
		if opts.deviceNo == "" {
			return 0, nil, errors.New("device-switch-card 需要传入 -device-no")
		}
		if opts.iccid == "" {
			return 0, nil, errors.New("device-switch-card 需要传入 -iccid")
		}
		return c.switchDeviceCard(ctx, opts.deviceNo, opts.iccid)
	case "device-reboot":
		if opts.deviceNo == "" {
			return 0, nil, errors.New("device-reboot 需要传入 -device-no")
		}
		return c.rebootDevice(ctx, opts.deviceNo)
	case "device-reset":
		if opts.deviceNo == "" {
			return 0, nil, errors.New("device-reset 需要传入 -device-no")
		}
		return c.resetDevice(ctx, opts.deviceNo)
	default:
		return 0, nil, fmt.Errorf("未知 action：%s", opts.action)
	}
}

func (c *client) queryRealnameStatus(ctx context.Context, cardNo string) (int, []byte, error) {
	query := url.Values{}
	query.Add("card_no", cardNo)
	return c.get(ctx, "/api/open/v1/cards/realname-status", query)
}

func (c *client) queryCardStatus(ctx context.Context, cardNo string) (int, []byte, error) {
	query := url.Values{}
	query.Add("card_no", cardNo)
	return c.get(ctx, "/api/open/v1/cards/status", query)
}

func (c *client) queryCardTraffic(ctx context.Context, cardNo string) (int, []byte, error) {
	query := url.Values{}
	query.Add("card_no", cardNo)
	return c.get(ctx, "/api/open/v1/cards/traffic", query)
}

func (c *client) queryPackages(ctx context.Context, query url.Values) (int, []byte, error) {
	return c.get(ctx, "/api/open/v1/packages", query)
}

func (c *client) queryWalletBalance(ctx context.Context) (int, []byte, error) {
	return c.get(ctx, "/api/open/v1/wallet/balance", nil)
}

func (c *client) createWalletPackageOrder(ctx context.Context, body walletPackageOrderRequest) (int, []byte, error) {
	return c.postJSON(ctx, "/api/open/v1/wallet/package-orders", body)
}

func (c *client) queryWalletTransactions(ctx context.Context, query url.Values) (int, []byte, error) {
	return c.get(ctx, "/api/open/v1/wallet/transactions", query)
}

func (c *client) resumeCard(ctx context.Context, cardNo string) (int, []byte, error) {
	return c.postJSON(ctx, "/api/open/v1/cards/resume", cardResumeRequest{CardNo: cardNo})
}

func (c *client) queryDeviceTraffic(ctx context.Context, deviceNo string) (int, []byte, error) {
	query := url.Values{}
	query.Add("device_no", deviceNo)
	return c.get(ctx, "/api/open/v1/devices/traffic", query)
}

func (c *client) switchDeviceCard(ctx context.Context, deviceNo string, iccid string) (int, []byte, error) {
	return c.postJSON(ctx, "/api/open/v1/devices/switch-card", deviceSwitchCardRequest{DeviceNo: deviceNo, ICCID: iccid})
}

func (c *client) rebootDevice(ctx context.Context, deviceNo string) (int, []byte, error) {
	return c.postJSON(ctx, "/api/open/v1/devices/reboot", deviceOperationRequest{DeviceNo: deviceNo})
}

func (c *client) resetDevice(ctx context.Context, deviceNo string) (int, []byte, error) {
	return c.postJSON(ctx, "/api/open/v1/devices/reset", deviceOperationRequest{DeviceNo: deviceNo})
}

func (c *client) get(ctx context.Context, path string, query url.Values) (int, []byte, error) {
	return c.do(ctx, http.MethodGet, path, query, nil)
}

func (c *client) postJSON(ctx context.Context, path string, body any) (int, []byte, error) {
	bodyBytes, err := json.Marshal(body)
	if err != nil {
		return 0, nil, fmt.Errorf("序列化请求体失败：%w", err)
	}
	return c.do(ctx, http.MethodPost, path, nil, bodyBytes)
}

func (c *client) do(ctx context.Context, method string, path string, query url.Values, body []byte) (int, []byte, error) {
	endpoint, err := url.Parse(c.baseURL)
	if err != nil {
		return 0, nil, fmt.Errorf("解析 base-url 失败：%w", err)
	}
	endpoint.Path = strings.TrimRight(endpoint.Path, "/") + path
	endpoint.RawQuery = canonicalQuery(query)

	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	nonce, err := newNonce()
	if err != nil {
		return 0, nil, err
	}
	sign := buildAgentSign(method, path, query, body, timestamp, nonce, c.account, c.password)

	var requestBody io.Reader
	if len(body) > 0 {
		requestBody = bytes.NewReader(body)
	}
	request, err := http.NewRequestWithContext(ctx, method, endpoint.String(), requestBody)
	if err != nil {
		return 0, nil, fmt.Errorf("创建 HTTP 请求失败：%w", err)
	}

	request.Header.Set("Accept", "application/json")
	request.Header.Set("X-Agent-Account", c.account)
	request.Header.Set("X-Agent-Password", c.password)
	request.Header.Set("X-Agent-Timestamp", timestamp)
	request.Header.Set("X-Agent-Nonce", nonce)
	request.Header.Set("X-Agent-Sign", sign)
	if len(body) > 0 {
		request.Header.Set("Content-Type", "application/json")
	}

	response, err := c.httpClient.Do(request)
	if err != nil {
		return 0, nil, fmt.Errorf("发送 HTTP 请求失败：%w", err)
	}
	defer response.Body.Close()

	responseBody, err := io.ReadAll(response.Body)
	if err != nil {
		return response.StatusCode, nil, fmt.Errorf("读取响应失败：%w", err)
	}
	return response.StatusCode, responseBody, nil
}

func buildAgentSign(method string, path string, query url.Values, body []byte, timestamp string, nonce string, account string, password string) string {
	bodyHash := sha256.Sum256(body)
	signPayload := strings.Join([]string{
		strings.ToUpper(method),
		path,
		canonicalQuery(query),
		hex.EncodeToString(bodyHash[:]),
		timestamp,
		nonce,
		account,
	}, "\n")

	mac := hmac.New(sha256.New, []byte(password))
	mac.Write([]byte(signPayload))
	return hex.EncodeToString(mac.Sum(nil))
}

func canonicalQuery(query url.Values) string {
	if len(query) == 0 {
		return ""
	}

	keys := make([]string, 0, len(query))
	for key := range query {
		if strings.EqualFold(key, "sign") {
			continue
		}
		keys = append(keys, key)
	}
	sort.Strings(keys)

	parts := make([]string, 0)
	for _, key := range keys {
		values := append([]string(nil), query[key]...)
		sort.Strings(values)
		for _, value := range values {
			parts = append(parts, url.QueryEscape(key)+"="+url.QueryEscape(value))
		}
	}
	return strings.Join(parts, "&")
}

func newNonce() (string, error) {
	bytes := make([]byte, 16)
	if _, err := rand.Read(bytes); err != nil {
		return "", fmt.Errorf("生成 nonce 失败：%w", err)
	}
	return strconv.FormatInt(time.Now().UnixNano(), 10) + "-" + hex.EncodeToString(bytes), nil
}

func packageQuery(opts runOptions) url.Values {
	query := url.Values{}
	addPositiveInt(query, "page", opts.page)
	addPositiveInt(query, "page_size", opts.pageSize)
	addString(query, "package_type", opts.packageType)
	if opts.seriesID >= 0 {
		query.Add("series_id", strconv.Itoa(opts.seriesID))
	}
	addString(query, "package_name", opts.packageName)
	return query
}

func walletTransactionQuery(opts runOptions) url.Values {
	query := url.Values{}
	addPositiveInt(query, "page", opts.page)
	addPositiveInt(query, "page_size", opts.pageSize)
	addString(query, "transaction_type", opts.transactionType)
	addString(query, "start_date", opts.startDate)
	addString(query, "end_date", opts.endDate)
	return query
}

func addPositiveInt(query url.Values, key string, value int) {
	if value > 0 {
		query.Add(key, strconv.Itoa(value))
	}
}

func addString(query url.Values, key string, value string) {
	if value != "" {
		query.Add(key, value)
	}
}

func buildPackageOrderRequest(cardNos string, packageCode string) (walletPackageOrderRequest, error) {
	if strings.TrimSpace(cardNos) == "" {
		return walletPackageOrderRequest{}, errors.New("package-order 需要传入 -card-nos")
	}
	if strings.TrimSpace(packageCode) == "" {
		return walletPackageOrderRequest{}, errors.New("package-order 需要传入 -package-code")
	}

	items := strings.Split(cardNos, ",")
	cards := make([]string, 0, len(items))
	for _, item := range items {
		cardNo := strings.TrimSpace(item)
		if cardNo != "" {
			cards = append(cards, cardNo)
		}
	}
	if len(cards) == 0 {
		return walletPackageOrderRequest{}, errors.New("package-order 的 -card-nos 不能为空")
	}
	if len(cards) > 100 {
		return walletPackageOrderRequest{}, errors.New("package-order 的 -card-nos 最多支持 100 张卡")
	}

	return walletPackageOrderRequest{
		CardNos:     cards,
		PackageCode: strings.TrimSpace(packageCode),
	}, nil
}

func validateDateRange(startDate string, endDate string) error {
	if startDate != "" {
		if _, err := time.Parse(time.DateOnly, startDate); err != nil {
			return fmt.Errorf("start-date 格式必须为 YYYY-MM-DD：%w", err)
		}
	}
	if endDate != "" {
		if _, err := time.Parse(time.DateOnly, endDate); err != nil {
			return fmt.Errorf("end-date 格式必须为 YYYY-MM-DD：%w", err)
		}
	}
	return nil
}

func envOrDefault(key string, fallback string) string {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}
	return value
}

func printJSON(body []byte) {
	var raw json.RawMessage
	if err := json.Unmarshal(body, &raw); err != nil {
		fmt.Println(string(body))
		return
	}

	var pretty bytes.Buffer
	if err := json.Indent(&pretty, raw, "", "  "); err != nil {
		fmt.Println(string(body))
		return
	}
	fmt.Println(pretty.String())
}
