package services

import (
	"context"
	"crypto/ecdsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/androidpublisher/v3"
	"google.golang.org/api/option"

	"github.com/treytartt/honeydue-api/internal/config"
)

// IAP validation errors
var (
	ErrIAPNotConfigured       = errors.New("IAP validation not configured")
	ErrInvalidReceipt         = errors.New("invalid receipt data")
	ErrInvalidPurchaseToken   = errors.New("invalid purchase token")
	ErrSubscriptionExpired    = errors.New("subscription has expired")
	ErrSubscriptionCancelled  = errors.New("subscription was cancelled")
	ErrInvalidProductID       = errors.New("invalid product ID")
	ErrReceiptValidationFailed = errors.New("receipt validation failed")
)

// AppleIAPClient handles Apple App Store Server API validation
type AppleIAPClient struct {
	keyID      string
	issuerID   string
	bundleID   string
	privateKey *ecdsa.PrivateKey
	sandbox    bool
	httpClient *http.Client // P-07: Reused across requests
}

// GoogleIAPClient handles Google Play Developer API validation
type GoogleIAPClient struct {
	service     *androidpublisher.Service
	packageName string
}

// AppleTransactionInfo represents decoded transaction info from Apple
type AppleTransactionInfo struct {
	TransactionID               string `json:"transactionId"`
	OriginalTransactionID       string `json:"originalTransactionId"`
	ProductID                   string `json:"productId"`
	PurchaseDate                int64  `json:"purchaseDate"`
	ExpiresDate                 int64  `json:"expiresDate"`
	Type                        string `json:"type"` // "Auto-Renewable Subscription"
	InAppOwnershipType          string `json:"inAppOwnershipType"`
	SignedDate                  int64  `json:"signedDate"`
	Environment                 string `json:"environment"` // "Sandbox" or "Production"
	BundleID                    string `json:"bundleId"`
	RevocationDate              *int64 `json:"revocationDate,omitempty"`
	RevocationReason            *int   `json:"revocationReason,omitempty"`
}

// AppleValidationResult contains the result of Apple receipt validation
type AppleValidationResult struct {
	Valid            bool
	TransactionID    string
	ProductID        string
	ExpiresAt        time.Time
	IsTrialPeriod    bool
	AutoRenewEnabled bool
	Environment      string
}

// GoogleValidationResult contains the result of Google token validation
type GoogleValidationResult struct {
	Valid             bool
	OrderID           string
	ProductID         string
	ExpiresAt         time.Time
	AutoRenewing      bool
	PaymentState      int64
	CancelReason      int64
	AcknowledgedState bool
}

// NewAppleIAPClient creates a new Apple IAP validation client
func NewAppleIAPClient(cfg config.AppleIAPConfig) (*AppleIAPClient, error) {
	if cfg.KeyPath == "" || cfg.KeyID == "" || cfg.IssuerID == "" || cfg.BundleID == "" {
		return nil, ErrIAPNotConfigured
	}

	// Read the private key
	keyData, err := os.ReadFile(cfg.KeyPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read Apple IAP key: %w", err)
	}

	// Parse the PEM-encoded private key
	block, _ := pem.Decode(keyData)
	if block == nil {
		return nil, fmt.Errorf("failed to decode PEM block")
	}

	privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("failed to parse private key: %w", err)
	}

	ecdsaKey, ok := privateKey.(*ecdsa.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("private key is not ECDSA")
	}

	return &AppleIAPClient{
		keyID:      cfg.KeyID,
		issuerID:   cfg.IssuerID,
		bundleID:   cfg.BundleID,
		privateKey: ecdsaKey,
		sandbox:    cfg.Sandbox,
		httpClient: &http.Client{Timeout: 30 * time.Second}, // P-07: Single client reused across requests
	}, nil
}

// generateJWT creates a signed JWT for App Store Server API authentication
func (c *AppleIAPClient) generateJWT() (string, error) {
	now := time.Now()
	claims := jwt.MapClaims{
		"iss": c.issuerID,
		"iat": now.Unix(),
		"exp": now.Add(60 * time.Minute).Unix(),
		"aud": "appstoreconnect-v1",
		"bid": c.bundleID,
	}

	token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
	token.Header["kid"] = c.keyID

	return token.SignedString(c.privateKey)
}

// getBaseURL returns the appropriate App Store Server API URL
func (c *AppleIAPClient) getBaseURL() string {
	if c.sandbox {
		return "https://api.storekit-sandbox.itunes.apple.com"
	}
	return "https://api.storekit.itunes.apple.com"
}

// ValidateTransaction validates a transaction ID with Apple's servers
func (c *AppleIAPClient) ValidateTransaction(ctx context.Context, transactionID string) (*AppleValidationResult, error) {
	token, err := c.generateJWT()
	if err != nil {
		return nil, fmt.Errorf("failed to generate JWT: %w", err)
	}

	// Get transaction info
	url := fmt.Sprintf("%s/inApps/v1/transactions/%s", c.getBaseURL(), transactionID)

	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	// P-07: Reuse the single http.Client instead of creating one per request
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to call Apple API: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("Apple API returned status %d: %s", resp.StatusCode, string(body))
	}

	// Parse the response
	var response struct {
		SignedTransactionInfo string `json:"signedTransactionInfo"`
	}
	if err := json.Unmarshal(body, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	// Decode the signed transaction info (it's a JWS)
	transactionInfo, err := c.decodeSignedTransaction(response.SignedTransactionInfo)
	if err != nil {
		return nil, err
	}

	// Validate bundle ID
	if transactionInfo.BundleID != c.bundleID {
		return nil, ErrInvalidReceipt
	}

	// Check if revoked
	if transactionInfo.RevocationDate != nil {
		return &AppleValidationResult{
			Valid: false,
		}, ErrSubscriptionCancelled
	}

	expiresAt := time.Unix(transactionInfo.ExpiresDate/1000, 0)

	return &AppleValidationResult{
		Valid:         true,
		TransactionID: transactionInfo.TransactionID,
		ProductID:     transactionInfo.ProductID,
		ExpiresAt:     expiresAt,
		Environment:   transactionInfo.Environment,
	}, nil
}

// ValidateReceipt validates an App Store receipt (base64-encoded)
// This is the modern approach using the /verifyReceipt endpoint compatibility
// For new integrations, prefer ValidateTransaction with transaction IDs
func (c *AppleIAPClient) ValidateReceipt(ctx context.Context, receiptData string) (*AppleValidationResult, error) {
	token, err := c.generateJWT()
	if err != nil {
		return nil, fmt.Errorf("failed to generate JWT: %w", err)
	}

	// Decode the receipt to get transaction history
	// The receiptData from StoreKit 2 is typically a signed transaction
	// For StoreKit 1, we need to use the legacy verifyReceipt endpoint

	// Try to decode as a signed transaction first (StoreKit 2)
	if strings.Contains(receiptData, ".") {
		// This looks like a JWS - try to decode it directly
		transactionInfo, err := c.decodeSignedTransaction(receiptData)
		if err == nil {
			expiresAt := time.Unix(transactionInfo.ExpiresDate/1000, 0)
			return &AppleValidationResult{
				Valid:         true,
				TransactionID: transactionInfo.TransactionID,
				ProductID:     transactionInfo.ProductID,
				ExpiresAt:     expiresAt,
				Environment:   transactionInfo.Environment,
			}, nil
		}
	}

	// For legacy receipts, use the subscription status endpoint
	// First, we need to find the original transaction ID from the receipt
	// This requires using the App Store Server API's history endpoint

	url := fmt.Sprintf("%s/inApps/v1/history/%s", c.getBaseURL(), c.bundleID)

	// Create request body
	requestBody := struct {
		ReceiptData string `json:"receipt-data"`
	}{
		ReceiptData: receiptData,
	}

	jsonBody, err := json.Marshal(requestBody)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(string(jsonBody)))
	if err != nil {
		return nil, err
	}

	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	// P-07: Reuse the single http.Client
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to call Apple API: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		// If history endpoint fails, try legacy validation
		return c.validateLegacyReceipt(ctx, receiptData)
	}

	var historyResponse struct {
		SignedTransactions []string `json:"signedTransactions"`
		HasMore            bool     `json:"hasMore"`
	}
	if err := json.Unmarshal(body, &historyResponse); err != nil {
		return nil, fmt.Errorf("failed to parse history response: %w", err)
	}

	if len(historyResponse.SignedTransactions) == 0 {
		return nil, ErrInvalidReceipt
	}

	// Get the most recent transaction
	latestTransaction := historyResponse.SignedTransactions[len(historyResponse.SignedTransactions)-1]
	transactionInfo, err := c.decodeSignedTransaction(latestTransaction)
	if err != nil {
		return nil, err
	}

	expiresAt := time.Unix(transactionInfo.ExpiresDate/1000, 0)

	return &AppleValidationResult{
		Valid:         true,
		TransactionID: transactionInfo.TransactionID,
		ProductID:     transactionInfo.ProductID,
		ExpiresAt:     expiresAt,
		Environment:   transactionInfo.Environment,
	}, nil
}

// validateLegacyReceipt uses Apple's legacy verifyReceipt endpoint.
// It delegates to validateLegacyReceiptWithSandbox using the client's
// configured sandbox setting. This avoids mutating the struct field
// during the sandbox-retry flow, which caused a data race when
// multiple goroutines shared the same AppleIAPClient.
func (c *AppleIAPClient) validateLegacyReceipt(ctx context.Context, receiptData string) (*AppleValidationResult, error) {
	return c.validateLegacyReceiptWithSandbox(ctx, receiptData, c.sandbox)
}

// validateLegacyReceiptWithSandbox performs legacy receipt validation against
// the specified environment. The sandbox parameter is passed by value (not
// stored on the struct) so this function is safe for concurrent use.
func (c *AppleIAPClient) validateLegacyReceiptWithSandbox(ctx context.Context, receiptData string, useSandbox bool) (*AppleValidationResult, error) {
	url := "https://buy.itunes.apple.com/verifyReceipt"
	if useSandbox {
		url = "https://sandbox.itunes.apple.com/verifyReceipt"
	}

	requestBody := map[string]string{
		"receipt-data": receiptData,
	}

	jsonBody, err := json.Marshal(requestBody)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(string(jsonBody)))
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/json")

	// P-07: Reuse the single http.Client
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to call Apple verifyReceipt: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	var legacyResponse struct {
		Status             int `json:"status"`
		LatestReceiptInfo  []struct {
			TransactionID         string `json:"transaction_id"`
			OriginalTransactionID string `json:"original_transaction_id"`
			ProductID             string `json:"product_id"`
			ExpiresDateMs         string `json:"expires_date_ms"`
			IsTrialPeriod         string `json:"is_trial_period"`
		} `json:"latest_receipt_info"`
		PendingRenewalInfo []struct {
			AutoRenewStatus string `json:"auto_renew_status"`
		} `json:"pending_renewal_info"`
		Environment string `json:"environment"`
	}

	if err := json.Unmarshal(body, &legacyResponse); err != nil {
		return nil, fmt.Errorf("failed to parse legacy response: %w", err)
	}

	// Status codes: 0 = valid, 21007 = sandbox receipt on production, 21008 = production receipt on sandbox
	if legacyResponse.Status == 21007 && !useSandbox {
		// Retry with sandbox -- pass sandbox=true as a parameter instead of
		// mutating c.sandbox, which avoids a data race.
		return c.validateLegacyReceiptWithSandbox(ctx, receiptData, true)
	}

	if legacyResponse.Status != 0 {
		return nil, fmt.Errorf("%w: status code %d", ErrReceiptValidationFailed, legacyResponse.Status)
	}

	if len(legacyResponse.LatestReceiptInfo) == 0 {
		return nil, ErrInvalidReceipt
	}

	// Find the latest non-expired subscription
	var latestReceipt = legacyResponse.LatestReceiptInfo[len(legacyResponse.LatestReceiptInfo)-1]

	var expiresMs int64
	fmt.Sscanf(latestReceipt.ExpiresDateMs, "%d", &expiresMs)
	expiresAt := time.Unix(expiresMs/1000, 0)

	autoRenew := false
	if len(legacyResponse.PendingRenewalInfo) > 0 {
		autoRenew = legacyResponse.PendingRenewalInfo[0].AutoRenewStatus == "1"
	}

	return &AppleValidationResult{
		Valid:            true,
		TransactionID:    latestReceipt.TransactionID,
		ProductID:        latestReceipt.ProductID,
		ExpiresAt:        expiresAt,
		IsTrialPeriod:    latestReceipt.IsTrialPeriod == "true",
		AutoRenewEnabled: autoRenew,
		Environment:      legacyResponse.Environment,
	}, nil
}

// decodeSignedTransaction decodes a JWS signed transaction from Apple
func (c *AppleIAPClient) decodeSignedTransaction(signedTransaction string) (*AppleTransactionInfo, error) {
	// The signed transaction is a JWS (JSON Web Signature)
	// Format: header.payload.signature
	parts := strings.Split(signedTransaction, ".")
	if len(parts) != 3 {
		return nil, fmt.Errorf("invalid JWS format")
	}

	// Decode the payload (base64url encoded)
	payload, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return nil, fmt.Errorf("failed to decode payload: %w", err)
	}

	var transactionInfo AppleTransactionInfo
	if err := json.Unmarshal(payload, &transactionInfo); err != nil {
		return nil, fmt.Errorf("failed to parse transaction info: %w", err)
	}

	return &transactionInfo, nil
}

// NewGoogleIAPClient creates a new Google IAP validation client
func NewGoogleIAPClient(ctx context.Context, cfg config.GoogleIAPConfig) (*GoogleIAPClient, error) {
	if cfg.ServiceAccountPath == "" || cfg.PackageName == "" {
		return nil, ErrIAPNotConfigured
	}

	// Read the service account JSON
	serviceAccountJSON, err := os.ReadFile(cfg.ServiceAccountPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read service account file: %w", err)
	}

	// Create credentials
	creds, err := google.CredentialsFromJSON(ctx, serviceAccountJSON, androidpublisher.AndroidpublisherScope)
	if err != nil {
		return nil, fmt.Errorf("failed to create credentials: %w", err)
	}

	// Create the Android Publisher service
	service, err := androidpublisher.NewService(ctx, option.WithCredentials(creds))
	if err != nil {
		return nil, fmt.Errorf("failed to create Android Publisher service: %w", err)
	}

	return &GoogleIAPClient{
		service:     service,
		packageName: cfg.PackageName,
	}, nil
}

// ValidateSubscription validates a Google Play subscription purchase token
func (c *GoogleIAPClient) ValidateSubscription(ctx context.Context, subscriptionID, purchaseToken string) (*GoogleValidationResult, error) {
	// Call Google Play Developer API to verify the subscription
	subscription, err := c.service.Purchases.Subscriptions.Get(
		c.packageName,
		subscriptionID,
		purchaseToken,
	).Context(ctx).Do()

	if err != nil {
		return nil, fmt.Errorf("failed to validate subscription: %w", err)
	}

	// ExpiryTimeMillis is in milliseconds
	expiresAt := time.Unix(subscription.ExpiryTimeMillis/1000, 0)

	// Dereference pointer values safely
	var paymentState int64
	if subscription.PaymentState != nil {
		paymentState = *subscription.PaymentState
	}

	result := &GoogleValidationResult{
		Valid:             true,
		OrderID:           subscription.OrderId,
		ProductID:         subscriptionID,
		ExpiresAt:         expiresAt,
		AutoRenewing:      subscription.AutoRenewing,
		PaymentState:      paymentState,
		CancelReason:      subscription.CancelReason,
		AcknowledgedState: subscription.AcknowledgementState == 1,
	}

	// Check if subscription is valid
	now := time.Now()
	if expiresAt.Before(now) {
		return result, ErrSubscriptionExpired
	}

	// Cancel reason: 0 = user cancelled, 1 = system cancelled
	if subscription.CancelReason > 0 {
		result.Valid = false
		return result, ErrSubscriptionCancelled
	}

	return result, nil
}

// ValidatePurchaseToken validates a purchase token, attempting to determine the subscription ID
// This is a convenience method when the client only sends the token
func (c *GoogleIAPClient) ValidatePurchaseToken(ctx context.Context, purchaseToken string, knownSubscriptionIDs []string) (*GoogleValidationResult, error) {
	// Try each known subscription ID until one works
	for _, subID := range knownSubscriptionIDs {
		result, err := c.ValidateSubscription(ctx, subID, purchaseToken)
		if err == nil {
			return result, nil
		}
		// If it's not a "not found" error, return the error
		if !strings.Contains(err.Error(), "notFound") && !strings.Contains(err.Error(), "404") {
			continue
		}
	}

	return nil, ErrInvalidPurchaseToken
}

// AcknowledgeSubscription acknowledges a subscription purchase
// This must be called within 3 days of purchase or the subscription is refunded
func (c *GoogleIAPClient) AcknowledgeSubscription(ctx context.Context, subscriptionID, purchaseToken string) error {
	err := c.service.Purchases.Subscriptions.Acknowledge(
		c.packageName,
		subscriptionID,
		purchaseToken,
		&androidpublisher.SubscriptionPurchasesAcknowledgeRequest{},
	).Context(ctx).Do()

	if err != nil {
		return fmt.Errorf("failed to acknowledge subscription: %w", err)
	}

	return nil
}
