package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"
)

type remoteOrder struct {
	OrderNo     string      `json:"order_no"`
	OutOrderNo  string      `json:"out_order_no"`
	OrderMoney  json.Number `json:"order_money"`
	OrderStatus string      `json:"order_status"`
	PaymentTime *int64      `json:"payment_time"`
	CreateTime  int64       `json:"create_time"`
}

type recoveryFile struct {
	Orders []recoveryOrder `json:"orders"`
}

type recoveryOrder struct {
	OutOrderNo      string `json:"out_order_no"`
	OrderNo         string `json:"order_no"`
	OrderStatus     string `json:"order_status"`
	SubscribeId     int64  `json:"subscribe_id"`
	ExpireAt        int64  `json:"expire_at"`
	Quantity        int64  `json:"quantity"`
	PaidAt          int64  `json:"paid_at,omitempty"`
	Note            string `json:"note,omitempty"`
	PaymentOrderNo  string `json:"payment_order_no,omitempty"`
	OrderMoneyCents int64  `json:"order_money_cents,omitempty"`
}

type adminOrderResponse struct {
	Code uint32 `json:"code"`
	Msg  string `json:"msg"`
	Data struct {
		Total int64        `json:"total"`
		List  []adminOrder `json:"list"`
	} `json:"data"`
}

type adminOrder struct {
	OrderNo string `json:"order_no"`
}

func main() {
	var (
		input       string
		output      string
		adminOrders string
		subscribeID int64
		onlyMissing bool
	)
	flag.StringVar(&input, "in", "aaa.json", "input payment platform orders JSON")
	flag.StringVar(&output, "out", "internal/recovery/recovery_orders.json", "output recovery orders JSON")
	flag.StringVar(&adminOrders, "admin-orders", "", "optional admin order list response JSON; when provided, matched out_order_no values are skipped")
	flag.Int64Var(&subscribeID, "subscribe-id", 1, "subscribe id to use for recovered subscriptions")
	flag.BoolVar(&onlyMissing, "only-missing", true, "only output payment success orders missing from admin order list when -admin-orders is set")
	flag.Parse()

	orders, err := readOrders(input)
	must(err)
	existingOrders := map[string]struct{}{}
	if adminOrders != "" {
		existingOrders, err = readAdminOrderSet(adminOrders)
		must(err)
	}

	daysByAmount := map[int64]int64{
		1953:  7,
		4193:  30,
		9093:  90,
		31500: 365,
	}

	out := recoveryFile{Orders: make([]recoveryOrder, 0, len(orders))}
	var skipped []string
	var matchedExisting int
	seen := make(map[string]struct{})
	for _, item := range orders {
		if strings.ToLower(strings.TrimSpace(item.OrderStatus)) != "success" {
			continue
		}
		localOrderNo := strings.TrimSpace(item.OutOrderNo)
		if localOrderNo == "" {
			skipped = append(skipped, fmt.Sprintf("%s missing out_order_no", item.OrderNo))
			continue
		}
		if _, ok := seen[localOrderNo]; ok {
			skipped = append(skipped, fmt.Sprintf("%s duplicate out_order_no", localOrderNo))
			continue
		}
		if _, exists := existingOrders[localOrderNo]; exists && onlyMissing {
			matchedExisting++
			seen[localOrderNo] = struct{}{}
			continue
		}
		amount := decimalYuanToCents(item.OrderMoney)
		days, ok := daysByAmount[amount]
		if !ok {
			skipped = append(skipped, fmt.Sprintf("%s unknown amount %s", localOrderNo, item.OrderMoney.String()))
			continue
		}
		base := item.CreateTime
		if item.PaymentTime != nil && *item.PaymentTime > 0 {
			base = *item.PaymentTime
		}
		if base <= 0 {
			skipped = append(skipped, fmt.Sprintf("%s missing payment/create time", localOrderNo))
			continue
		}
		seen[localOrderNo] = struct{}{}
		out.Orders = append(out.Orders, recoveryOrder{
			OutOrderNo:      localOrderNo,
			OrderNo:         strings.TrimSpace(item.OrderNo),
			OrderStatus:     "success",
			SubscribeId:     subscribeID,
			ExpireAt:        time.Unix(base, 0).Add(time.Duration(days) * 24 * time.Hour).UnixMilli(),
			Quantity:        days,
			PaidAt:          base,
			Note:            "data recovery",
			PaymentOrderNo:  strings.TrimSpace(item.OrderNo),
			OrderMoneyCents: amount,
		})
	}

	sort.Slice(out.Orders, func(i, j int) bool {
		return out.Orders[i].OutOrderNo < out.Orders[j].OutOrderNo
	})

	must(writeJSON(output, out))
	fmt.Printf("converted recovery orders=%d matched_existing=%d skipped=%d output=%s\n", len(out.Orders), matchedExisting, len(skipped), output)
	for _, msg := range skipped {
		fmt.Printf("skip: %s\n", msg)
	}
}

func readAdminOrderSet(path string) (map[string]struct{}, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var resp adminOrderResponse
	if err := json.Unmarshal(data, &resp); err == nil && resp.Data.List != nil {
		if resp.Code != 0 && resp.Code != 200 {
			return nil, fmt.Errorf("admin order response failed: code=%d msg=%s", resp.Code, resp.Msg)
		}
		return adminOrdersToSet(resp.Data.List), nil
	}

	var list []adminOrder
	if err := json.Unmarshal(data, &list); err != nil {
		return nil, err
	}
	return adminOrdersToSet(list), nil
}

func adminOrdersToSet(list []adminOrder) map[string]struct{} {
	set := make(map[string]struct{}, len(list))
	for _, item := range list {
		orderNo := strings.TrimSpace(item.OrderNo)
		if orderNo != "" {
			set[orderNo] = struct{}{}
		}
	}
	return set
}

func readOrders(path string) ([]remoteOrder, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var orders []remoteOrder
	if err := json.Unmarshal(data, &orders); err != nil {
		return nil, err
	}
	return orders, nil
}

func writeJSON(path string, data recoveryFile) error {
	bytes, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		return err
	}
	bytes = append(bytes, '\n')
	return os.WriteFile(path, bytes, 0644)
}

func decimalYuanToCents(n json.Number) int64 {
	s := strings.TrimSpace(n.String())
	if s == "" {
		return 0
	}
	parts := strings.SplitN(s, ".", 2)
	yuan, _ := strconv.ParseInt(parts[0], 10, 64)
	cents := yuan * 100
	if len(parts) == 1 {
		return cents
	}
	frac := parts[1]
	if len(frac) > 2 {
		frac = frac[:2]
	}
	for len(frac) < 2 {
		frac += "0"
	}
	f, _ := strconv.ParseInt(frac, 10, 64)
	if strings.HasPrefix(parts[0], "-") {
		return cents - f
	}
	return cents + f
}

func must(err error) {
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
