// Package googleorders 提供 Play Developer API Orders 资源的最小只读客户端。
// hyapp 自有 App（Lalu 等）的实付同步走 wallet-service；这里只服务 legacy 账单源（Yumi/Aslan），
// 它们的支付发生在外部平台，admin 需要用各自 Play Console 的服务账号直接查订单实付。
package googleorders

import (
	"context"
	"crypto/rsa"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

const (
	androidPublisherScope = "https://www.googleapis.com/auth/androidpublisher"
	defaultAPIBaseURL     = "https://androidpublisher.googleapis.com"
	defaultTokenURL       = "https://oauth2.googleapis.com/token"
)

// Order 是 orders.get 的实付快照；金额统一为实付币种微单位。
type Order struct {
	OrderID          string
	State            string
	BuyerCountry     string
	CurrencyCode     string
	TotalAmountMicro int64
	TaxAmountMicro   int64
	// NetAmountMicro 是 Google 扣除服务费和代缴税后的开发者净收入；接口未返回时为 0。
	NetAmountMicro int64
}

// Config 描述一个 Play Console 服务账号；ServiceAccountJSON 优先于 ServiceAccountFile。
type Config struct {
	PackageName        string
	ServiceAccountJSON string
	ServiceAccountFile string
	HTTPTimeout        time.Duration
}

type serviceAccount struct {
	ClientEmail string `json:"client_email"`
	PrivateKey  string `json:"private_key"`
	TokenURI    string `json:"token_uri"`
}

// Client 按服务账号缓存 OAuth token；一个 legacy App 一个实例。
type Client struct {
	httpClient  *http.Client
	packageName string
	tokenURL    string
	email       string
	privateKey  *rsa.PrivateKey

	mu          sync.Mutex
	accessToken string
	expiresAt   time.Time
}

// New 创建 legacy Google Orders 客户端；凭证不完整时返回错误，由调用方决定是否降级为“未配置”。
func New(cfg Config) (*Client, error) {
	body := strings.TrimSpace(cfg.ServiceAccountJSON)
	if body == "" && strings.TrimSpace(cfg.ServiceAccountFile) != "" {
		fileBody, err := os.ReadFile(strings.TrimSpace(cfg.ServiceAccountFile))
		if err != nil {
			return nil, err
		}
		body = string(fileBody)
	}
	if body == "" {
		return nil, errors.New("google service account is not configured")
	}
	var account serviceAccount
	if err := json.Unmarshal([]byte(body), &account); err != nil {
		return nil, fmt.Errorf("parse google service account: %w", err)
	}
	account.ClientEmail = strings.TrimSpace(account.ClientEmail)
	account.PrivateKey = strings.TrimSpace(account.PrivateKey)
	if account.ClientEmail == "" || account.PrivateKey == "" {
		return nil, errors.New("google service account is incomplete")
	}
	privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(account.PrivateKey))
	if err != nil {
		return nil, fmt.Errorf("parse google private key: %w", err)
	}
	tokenURL := strings.TrimSpace(account.TokenURI)
	if tokenURL == "" {
		tokenURL = defaultTokenURL
	}
	timeout := cfg.HTTPTimeout
	if timeout <= 0 {
		timeout = 10 * time.Second
	}
	packageName := strings.TrimSpace(cfg.PackageName)
	if packageName == "" {
		return nil, errors.New("google package name is required")
	}
	return &Client{
		httpClient:  &http.Client{Timeout: timeout},
		packageName: packageName,
		tokenURL:    tokenURL,
		email:       account.ClientEmail,
		privateKey:  privateKey,
	}, nil
}

// GetOrder 查询单笔订单实付明细；服务账号需要该 Play Console 的“查看财务数据”权限。
func (c *Client) GetOrder(ctx context.Context, orderID string) (Order, error) {
	if c == nil {
		return Order{}, errors.New("google orders client is not configured")
	}
	orderID = strings.TrimSpace(orderID)
	if orderID == "" {
		return Order{}, errors.New("google order id is required")
	}
	endpoint := fmt.Sprintf("%s/androidpublisher/v3/applications/%s/orders/%s",
		defaultAPIBaseURL, url.PathEscape(c.packageName), url.PathEscape(orderID))
	body, err := c.getJSON(ctx, endpoint)
	if err != nil {
		return Order{}, err
	}
	var parsed orderResource
	if err := json.Unmarshal(body, &parsed); err != nil {
		return Order{}, err
	}
	return orderFromResource(parsed), nil
}

// GetOrders uses Orders batchGet for backlog repair: one request can fetch up to 1000 distinct orders.
// The caller falls back to single-order requests if Google rejects the whole batch because of one stale ID.
func (c *Client) GetOrders(ctx context.Context, orderIDs []string) (map[string]Order, error) {
	if c == nil {
		return nil, errors.New("google orders client is not configured")
	}
	cleaned := make([]string, 0, len(orderIDs))
	seen := make(map[string]struct{}, len(orderIDs))
	for _, orderID := range orderIDs {
		orderID = strings.TrimSpace(orderID)
		if orderID == "" {
			continue
		}
		if _, ok := seen[orderID]; ok {
			continue
		}
		seen[orderID] = struct{}{}
		cleaned = append(cleaned, orderID)
	}
	if len(cleaned) == 0 {
		return map[string]Order{}, nil
	}
	if len(cleaned) > 1000 {
		return nil, errors.New("too many google order ids")
	}
	query := url.Values{}
	for _, orderID := range cleaned {
		query.Add("orderIds", orderID)
	}
	endpoint := fmt.Sprintf("%s/androidpublisher/v3/applications/%s/orders:batchGet?%s",
		defaultAPIBaseURL, url.PathEscape(c.packageName), query.Encode())
	body, err := c.getJSON(ctx, endpoint)
	if err != nil {
		return nil, err
	}
	var parsed struct {
		Orders []orderResource `json:"orders"`
	}
	if err := json.Unmarshal(body, &parsed); err != nil {
		return nil, err
	}
	orders := make(map[string]Order, len(parsed.Orders))
	for _, item := range parsed.Orders {
		order := orderFromResource(item)
		if order.OrderID != "" {
			orders[order.OrderID] = order
		}
	}
	return orders, nil
}

func orderFromResource(parsed orderResource) Order {
	order := Order{
		OrderID:          parsed.OrderID,
		State:            parsed.State,
		BuyerCountry:     parsed.BuyerAddress.BuyerCountry,
		CurrencyCode:     parsed.Total.CurrencyCode,
		TotalAmountMicro: parsed.Total.micro(),
		TaxAmountMicro:   parsed.Tax.micro(),
		NetAmountMicro:   parsed.DeveloperRevenueInBuyerCurrency.micro(),
	}
	if order.CurrencyCode == "" {
		order.CurrencyCode = parsed.Tax.CurrencyCode
	}
	return order
}

func (c *Client) getJSON(ctx context.Context, endpoint string) ([]byte, error) {
	token, err := c.token(ctx)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+token)
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, errors.New("google play api request failed")
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
	if err != nil {
		return nil, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		switch resp.StatusCode {
		case http.StatusNotFound, http.StatusBadRequest:
			return nil, errors.New("google order is not found")
		case http.StatusUnauthorized, http.StatusForbidden:
			return nil, errors.New("google play api is not authorized (需要查看财务数据权限)")
		default:
			return nil, fmt.Errorf("google play api returned %d", resp.StatusCode)
		}
	}
	return body, nil
}

func (c *Client) token(ctx context.Context) (string, error) {
	c.mu.Lock()
	if c.accessToken != "" && time.Now().Before(c.expiresAt.Add(-60*time.Second)) {
		token := c.accessToken
		c.mu.Unlock()
		return token, nil
	}
	c.mu.Unlock()

	now := time.Now()
	claims := jwt.MapClaims{
		"iss":   c.email,
		"scope": androidPublisherScope,
		"aud":   c.tokenURL,
		"iat":   now.Unix(),
		"exp":   now.Add(time.Hour).Unix(),
	}
	assertion, err := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(c.privateKey)
	if err != nil {
		return "", err
	}
	form := url.Values{}
	form.Set("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
	form.Set("assertion", assertion)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.tokenURL, strings.NewReader(form.Encode()))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return "", errors.New("google oauth token request failed")
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
	if err != nil {
		return "", err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return "", errors.New("google oauth token request failed")
	}
	var tokenResp struct {
		AccessToken string `json:"access_token"`
		ExpiresIn   int64  `json:"expires_in"`
	}
	if err := json.Unmarshal(body, &tokenResp); err != nil {
		return "", err
	}
	if tokenResp.AccessToken == "" {
		return "", errors.New("google oauth token response is incomplete")
	}
	expiresIn := time.Duration(tokenResp.ExpiresIn) * time.Second
	if expiresIn <= 0 {
		expiresIn = time.Hour
	}
	c.mu.Lock()
	c.accessToken = tokenResp.AccessToken
	c.expiresAt = now.Add(expiresIn)
	c.mu.Unlock()
	return tokenResp.AccessToken, nil
}

type orderResource struct {
	OrderID                         string       `json:"orderId"`
	State                           string       `json:"state"`
	BuyerAddress                    buyerAddress `json:"buyerAddress"`
	Total                           moneyAmount  `json:"total"`
	Tax                             moneyAmount  `json:"tax"`
	DeveloperRevenueInBuyerCurrency moneyAmount  `json:"developerRevenueInBuyerCurrency"`
}

type buyerAddress struct {
	BuyerCountry string `json:"buyerCountry"`
}

// moneyAmount 是 google.type.Money 的 JSON 形态；units 是字符串形式的整数金额，nanos 是 10^-9 小数部分。
type moneyAmount struct {
	CurrencyCode string      `json:"currencyCode"`
	Units        json.Number `json:"units"`
	Nanos        int64       `json:"nanos"`
}

func (m moneyAmount) micro() int64 {
	units, _ := m.Units.Int64()
	return units*1_000_000 + m.Nanos/1_000
}
