package googleplay

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

	"github.com/golang-jwt/jwt/v5"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/config"
	"hyapp/services/wallet-service/internal/domain/ledger"
)

const androidPublisherScope = "https://www.googleapis.com/auth/androidpublisher"

// Client implements the minimal Google Play Developer API calls needed by wallet confirm.
type Client struct {
	httpClient     *http.Client
	apiBaseURL     string
	tokenURL       string
	packageName    string
	consumeEnabled bool
	email          string
	privateKey     *rsa.PrivateKey

	mu          sync.Mutex
	accessToken string
	expiresAt   time.Time
}

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

// New creates a Google Play Developer API client from wallet-service config.
func New(cfg config.GooglePlayConfig) (*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)
	}
	var account serviceAccount
	if err := json.Unmarshal([]byte(body), &account); err != nil {
		return nil, fmt.Errorf("parse google play service account: %w", err)
	}
	account.ClientEmail = strings.TrimSpace(account.ClientEmail)
	account.PrivateKey = strings.TrimSpace(account.PrivateKey)
	account.TokenURI = strings.TrimSpace(account.TokenURI)
	if account.ClientEmail == "" || account.PrivateKey == "" {
		return nil, errors.New("google play service account is incomplete")
	}
	privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(account.PrivateKey))
	if err != nil {
		return nil, fmt.Errorf("parse google play private key: %w", err)
	}
	tokenURL := strings.TrimSpace(cfg.TokenURL)
	if tokenURL == "" {
		tokenURL = account.TokenURI
	}
	if tokenURL == "" {
		tokenURL = "https://oauth2.googleapis.com/token"
	}
	apiBaseURL := strings.TrimRight(strings.TrimSpace(cfg.APIBaseURL), "/")
	if apiBaseURL == "" {
		apiBaseURL = "https://androidpublisher.googleapis.com"
	}
	timeout := cfg.HTTPTimeout
	if timeout <= 0 {
		timeout = 10 * time.Second
	}
	return &Client{
		httpClient:     &http.Client{Timeout: timeout},
		apiBaseURL:     apiBaseURL,
		tokenURL:       tokenURL,
		packageName:    strings.TrimSpace(cfg.PackageName),
		consumeEnabled: cfg.ConsumeEnabled,
		email:          account.ClientEmail,
		privateKey:     privateKey,
	}, nil
}

func (c *Client) GetProductPurchase(ctx context.Context, packageName string, purchaseToken string) (ledger.GooglePlayPurchase, error) {
	if c == nil {
		return ledger.GooglePlayPurchase{}, xerr.New(xerr.Unavailable, "google play client is not configured")
	}
	packageName = c.normalizePackageName(packageName)
	purchaseToken = strings.TrimSpace(purchaseToken)
	if packageName == "" || purchaseToken == "" {
		return ledger.GooglePlayPurchase{}, xerr.New(xerr.InvalidArgument, "google play purchase request is incomplete")
	}
	endpoint := fmt.Sprintf("%s/androidpublisher/v3/applications/%s/purchases/productsv2/tokens/%s",
		c.apiBaseURL,
		url.PathEscape(packageName),
		url.PathEscape(purchaseToken),
	)
	body, err := c.doJSON(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return ledger.GooglePlayPurchase{}, err
	}
	var parsed productPurchaseV2
	if err := json.Unmarshal(body, &parsed); err != nil {
		return ledger.GooglePlayPurchase{}, err
	}
	purchase := ledger.GooglePlayPurchase{
		PackageName:            packageName,
		OrderID:                parsed.OrderID,
		PurchaseState:          parsed.PurchaseStateContext.PurchaseState,
		AcknowledgementState:   parsed.AcknowledgementState,
		PurchaseCompletionTime: parsed.PurchaseCompletionTime,
		RawJSON:                string(body),
	}
	if len(parsed.ProductLineItem) > 0 {
		purchase.ProductID = parsed.ProductLineItem[0].ProductID
		purchase.ConsumptionState = parsed.ProductLineItem[0].ProductOfferDetails.ConsumptionState
	}
	return purchase, nil
}

// GetOrder 拉取 Play Developer API Orders 资源，取回用户实付币种、实付总额、税额和开发者净收入。
// 需要服务账号在 Play Console 拥有“查看财务数据”权限，否则 Google 返回 403。
func (c *Client) GetOrder(ctx context.Context, packageName string, orderID string) (ledger.GooglePlayOrder, error) {
	if c == nil {
		return ledger.GooglePlayOrder{}, xerr.New(xerr.Unavailable, "google play client is not configured")
	}
	packageName = c.normalizePackageName(packageName)
	orderID = strings.TrimSpace(orderID)
	if packageName == "" || orderID == "" {
		return ledger.GooglePlayOrder{}, xerr.New(xerr.InvalidArgument, "google play order request is incomplete")
	}
	endpoint := fmt.Sprintf("%s/androidpublisher/v3/applications/%s/orders/%s",
		c.apiBaseURL,
		url.PathEscape(packageName),
		url.PathEscape(orderID),
	)
	body, err := c.doJSON(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return ledger.GooglePlayOrder{}, err
	}
	var parsed orderResource
	if err := json.Unmarshal(body, &parsed); err != nil {
		return ledger.GooglePlayOrder{}, err
	}
	return googlePlayOrderFromResource(parsed, string(body)), nil
}

// GetOrders fetches up to 1000 paid-detail snapshots with one Orders batchGet request.
// Service code falls back to single gets when Google rejects a mixed batch because one ID is stale.
func (c *Client) GetOrders(ctx context.Context, packageName string, orderIDs []string) (map[string]ledger.GooglePlayOrder, error) {
	if c == nil {
		return nil, xerr.New(xerr.Unavailable, "google play client is not configured")
	}
	packageName = c.normalizePackageName(packageName)
	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 packageName == "" || len(cleaned) == 0 {
		return map[string]ledger.GooglePlayOrder{}, nil
	}
	if len(cleaned) > 1000 {
		return nil, xerr.New(xerr.InvalidArgument, "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",
		c.apiBaseURL, url.PathEscape(packageName), query.Encode())
	body, err := c.doJSON(ctx, http.MethodGet, endpoint, nil)
	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]ledger.GooglePlayOrder, len(parsed.Orders))
	for _, item := range parsed.Orders {
		order := googlePlayOrderFromResource(item, "")
		if order.OrderID != "" {
			orders[order.OrderID] = order
		}
	}
	return orders, nil
}

func googlePlayOrderFromResource(parsed orderResource, rawJSON string) ledger.GooglePlayOrder {
	order := ledger.GooglePlayOrder{
		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(),
		RawJSON:          rawJSON,
	}
	if order.CurrencyCode == "" {
		order.CurrencyCode = parsed.Tax.CurrencyCode
	}
	return order
}

func (c *Client) ConsumeProduct(ctx context.Context, packageName string, productID string, purchaseToken string) error {
	if c == nil {
		return xerr.New(xerr.Unavailable, "google play client is not configured")
	}
	if !c.consumeEnabled {
		return xerr.New(xerr.Unavailable, "google play consume is disabled")
	}
	packageName = c.normalizePackageName(packageName)
	productID = strings.TrimSpace(productID)
	purchaseToken = strings.TrimSpace(purchaseToken)
	if packageName == "" || productID == "" || purchaseToken == "" {
		return xerr.New(xerr.InvalidArgument, "google play consume request is incomplete")
	}
	endpoint := fmt.Sprintf("%s/androidpublisher/v3/applications/%s/purchases/products/%s/tokens/%s:consume",
		c.apiBaseURL,
		url.PathEscape(packageName),
		url.PathEscape(productID),
		url.PathEscape(purchaseToken),
	)
	_, err := c.doJSON(ctx, http.MethodPost, endpoint, bytes.NewReader(nil))
	return err
}

func (c *Client) normalizePackageName(value string) string {
	value = strings.TrimSpace(value)
	if value != "" {
		return value
	}
	return c.packageName
}

func (c *Client) doJSON(ctx context.Context, method string, endpoint string, body io.Reader) ([]byte, error) {
	token, err := c.token(ctx)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequestWithContext(ctx, method, endpoint, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+token)
	if method != http.MethodGet {
		req.Header.Set("Content-Type", "application/json")
	}
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, xerr.New(xerr.Unavailable, "google play api request failed")
	}
	defer resp.Body.Close()
	respBody, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
	if readErr != nil {
		return nil, readErr
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		if resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusBadRequest {
			return nil, xerr.New(xerr.InvalidArgument, "google play purchase token is invalid")
		}
		if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
			return nil, xerr.New(xerr.Unavailable, "google play api is not authorized")
		}
		return nil, xerr.New(xerr.Unavailable, "google play api returned non-success")
	}
	return respBody, 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 "", xerr.New(xerr.Unavailable, "google oauth token request failed")
	}
	defer resp.Body.Close()
	respBody, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
	if readErr != nil {
		return "", readErr
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return "", xerr.New(xerr.Unavailable, "google oauth token request failed")
	}
	var tokenResp struct {
		AccessToken string `json:"access_token"`
		ExpiresIn   int64  `json:"expires_in"`
		TokenType   string `json:"token_type"`
	}
	if err := json.Unmarshal(respBody, &tokenResp); err != nil {
		return "", err
	}
	if tokenResp.AccessToken == "" {
		return "", xerr.New(xerr.Unavailable, "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 productPurchaseV2 struct {
	ProductLineItem        []productLineItem `json:"productLineItem"`
	OrderID                string            `json:"orderId"`
	PurchaseStateContext   purchaseState     `json:"purchaseStateContext"`
	PurchaseCompletionTime string            `json:"purchaseCompletionTime"`
	AcknowledgementState   string            `json:"acknowledgementState"`
}

type purchaseState struct {
	PurchaseState string `json:"purchaseState"`
}

type productLineItem struct {
	ProductID           string              `json:"productId"`
	ProductOfferDetails productOfferDetails `json:"productOfferDetails"`
}

type productOfferDetails struct {
	ConsumptionState string `json:"consumptionState"`
}

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
}
