package handlers

import (
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"math/big"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/labstack/echo/v4"
	"github.com/rs/zerolog/log"

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

// maxWebhookBodySize is the maximum allowed request body size for webhook
// payloads (1 MB). This prevents a malicious or misbehaving sender from
// forcing the server to allocate unbounded memory.
const maxWebhookBodySize = 1 << 20 // 1 MB

// SubscriptionWebhookHandler handles subscription webhook callbacks
type SubscriptionWebhookHandler struct {
	subscriptionRepo *repositories.SubscriptionRepository
	userRepo         *repositories.UserRepository
	webhookEventRepo *repositories.WebhookEventRepository
	appleRootCerts   []*x509.Certificate
	stripeService    *services.StripeService
	enabled          bool
}

// NewSubscriptionWebhookHandler creates a new webhook handler
func NewSubscriptionWebhookHandler(
	subscriptionRepo *repositories.SubscriptionRepository,
	userRepo *repositories.UserRepository,
	webhookEventRepo *repositories.WebhookEventRepository,
	enabled bool,
) *SubscriptionWebhookHandler {
	return &SubscriptionWebhookHandler{
		subscriptionRepo: subscriptionRepo,
		userRepo:         userRepo,
		webhookEventRepo: webhookEventRepo,
		enabled:          enabled,
	}
}

// SetStripeService sets the Stripe service for webhook handling
func (h *SubscriptionWebhookHandler) SetStripeService(stripeService *services.StripeService) {
	h.stripeService = stripeService
}

// ====================
// Apple App Store Server Notifications v2
// ====================

// AppleNotificationPayload represents the outer signed payload from Apple
type AppleNotificationPayload struct {
	SignedPayload string `json:"signedPayload"`
}

// AppleNotificationData represents the decoded notification data
type AppleNotificationData struct {
	NotificationType    string                      `json:"notificationType"`
	Subtype             string                      `json:"subtype"`
	NotificationUUID    string                      `json:"notificationUUID"`
	Data                AppleNotificationDataInner  `json:"data"`
	Version             string                      `json:"version"`
	SignedDate          int64                       `json:"signedDate"`
}

// AppleNotificationDataInner contains the transaction details
type AppleNotificationDataInner struct {
	AppAppleID            int64  `json:"appAppleId"`
	BundleID              string `json:"bundleId"`
	BundleVersion         string `json:"bundleVersion"`
	Environment           string `json:"environment"`
	SignedTransactionInfo string `json:"signedTransactionInfo"`
	SignedRenewalInfo     string `json:"signedRenewalInfo"`
}

// AppleTransactionInfo represents decoded transaction info
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"`
	AppAccountToken       string `json:"appAccountToken"` // Your user ID if set during purchase
	BundleID              string `json:"bundleId"`
	Environment           string `json:"environment"`
	RevocationDate        *int64 `json:"revocationDate,omitempty"`
	RevocationReason      *int   `json:"revocationReason,omitempty"`
}

// AppleRenewalInfo represents subscription renewal info
type AppleRenewalInfo struct {
	AutoRenewProductID string `json:"autoRenewProductId"`
	AutoRenewStatus    int    `json:"autoRenewStatus"` // 1 = will renew, 0 = turned off
	ExpirationIntent   int    `json:"expirationIntent"`
	IsInBillingRetry   bool   `json:"isInBillingRetryPeriod"`
}

// HandleAppleWebhook handles POST /api/subscription/webhook/apple/
func (h *SubscriptionWebhookHandler) HandleAppleWebhook(c echo.Context) error {
	if !h.enabled {
		log.Info().Msg("Apple Webhook: webhooks disabled by feature flag")
		return c.JSON(http.StatusOK, map[string]interface{}{"status": "webhooks_disabled"})
	}

	body, err := io.ReadAll(io.LimitReader(c.Request().Body, maxWebhookBodySize))
	if err != nil {
		log.Error().Err(err).Msg("Apple Webhook: Failed to read body")
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "failed to read request body"})
	}

	var payload AppleNotificationPayload
	if err := json.Unmarshal(body, &payload); err != nil {
		log.Error().Err(err).Msg("Apple Webhook: Failed to parse payload")
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "invalid payload"})
	}

	// Decode and verify the signed payload (JWS)
	notification, err := h.decodeAppleSignedPayload(payload.SignedPayload)
	if err != nil {
		log.Error().Err(err).Msg("Apple Webhook: Failed to decode signed payload")
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "invalid signed payload"})
	}

	log.Info().Str("type", notification.NotificationType).Str("subtype", notification.Subtype).Str("bundle", notification.Data.BundleID).Msg("Apple Webhook: Received notification")

	// Dedup check using notificationUUID
	if notification.NotificationUUID != "" {
		alreadyProcessed, err := h.webhookEventRepo.HasProcessed("apple", notification.NotificationUUID)
		if err != nil {
			log.Error().Err(err).Msg("Apple Webhook: Failed to check dedup")
			// Continue processing on dedup check failure (fail-open)
		} else if alreadyProcessed {
			log.Info().Str("uuid", notification.NotificationUUID).Msg("Apple Webhook: Duplicate event, skipping")
			return c.JSON(http.StatusOK, map[string]interface{}{"status": "duplicate"})
		}
	}

	// Verify bundle ID matches our app
	cfg := config.Get()
	if cfg != nil && cfg.AppleIAP.BundleID != "" {
		if notification.Data.BundleID != cfg.AppleIAP.BundleID {
			log.Warn().Str("got", notification.Data.BundleID).Str("expected", cfg.AppleIAP.BundleID).Msg("Apple Webhook: Bundle ID mismatch")
			return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "bundle ID mismatch"})
		}
	}

	// Decode transaction info
	transactionInfo, err := h.decodeAppleTransaction(notification.Data.SignedTransactionInfo)
	if err != nil {
		log.Error().Err(err).Msg("Apple Webhook: Failed to decode transaction")
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "invalid transaction info"})
	}

	// Decode renewal info if present
	var renewalInfo *AppleRenewalInfo
	if notification.Data.SignedRenewalInfo != "" {
		renewalInfo, _ = h.decodeAppleRenewalInfo(notification.Data.SignedRenewalInfo)
	}

	// Process the notification
	if err := h.processAppleNotification(notification, transactionInfo, renewalInfo); err != nil {
		log.Error().Err(err).Msg("Apple Webhook: Failed to process notification")
		// Still return 200 to prevent Apple from retrying
	}

	// Record processed event for dedup
	if notification.NotificationUUID != "" {
		if err := h.webhookEventRepo.RecordEvent("apple", notification.NotificationUUID, notification.NotificationType, ""); err != nil {
			log.Error().Err(err).Msg("Apple Webhook: Failed to record event")
		}
	}

	// Always return 200 OK to acknowledge receipt
	return c.JSON(http.StatusOK, map[string]interface{}{"status": "received"})
}

// decodeAppleSignedPayload verifies and decodes an Apple JWS payload.
// The JWS signature is verified before the payload is trusted.
func (h *SubscriptionWebhookHandler) decodeAppleSignedPayload(signedPayload string) (*AppleNotificationData, error) {
	// JWS format: header.payload.signature
	parts := strings.Split(signedPayload, ".")
	if len(parts) != 3 {
		return nil, fmt.Errorf("invalid JWS format")
	}

	// Verify the JWS signature before trusting the payload.
	if err := h.VerifyAppleSignature(signedPayload); err != nil {
		return nil, fmt.Errorf("Apple JWS signature verification failed: %w", err)
	}

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

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

	return &notification, nil
}

// decodeAppleTransaction decodes and verifies a signed transaction info JWS.
// The inner JWS signature is verified using the same Apple certificate chain
// validation as the outer notification payload.
func (h *SubscriptionWebhookHandler) decodeAppleTransaction(signedTransaction string) (*AppleTransactionInfo, error) {
	parts := strings.Split(signedTransaction, ".")
	if len(parts) != 3 {
		return nil, fmt.Errorf("invalid JWS format")
	}

	// S-16: Verify the inner JWS signature for signedTransactionInfo.
	// Apple signs each inner JWS independently with the same x5c certificate
	// chain as the outer notification, so the same verification applies.
	if err := h.VerifyAppleSignature(signedTransaction); err != nil {
		return nil, fmt.Errorf("Apple transaction JWS signature verification failed: %w", err)
	}

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

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

	return &info, nil
}

// decodeAppleRenewalInfo decodes and verifies a signed renewal info JWS.
// The inner JWS signature is verified using the same Apple certificate chain
// validation as the outer notification payload.
func (h *SubscriptionWebhookHandler) decodeAppleRenewalInfo(signedRenewal string) (*AppleRenewalInfo, error) {
	parts := strings.Split(signedRenewal, ".")
	if len(parts) != 3 {
		return nil, fmt.Errorf("invalid JWS format")
	}

	// S-16: Verify the inner JWS signature for signedRenewalInfo.
	if err := h.VerifyAppleSignature(signedRenewal); err != nil {
		return nil, fmt.Errorf("Apple renewal JWS signature verification failed: %w", err)
	}

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

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

	return &info, nil
}

// processAppleNotification handles the business logic for Apple notifications
func (h *SubscriptionWebhookHandler) processAppleNotification(
	notification *AppleNotificationData,
	transaction *AppleTransactionInfo,
	renewal *AppleRenewalInfo,
) error {
	// Find user by stored receipt data (original transaction ID)
	user, err := h.findUserByAppleTransaction(transaction.OriginalTransactionID)
	if err != nil {
		log.Warn().Err(err).Str("transaction_id", transaction.OriginalTransactionID).Msg("Apple Webhook: Could not find user for transaction")
		// Not an error - might be a transaction we don't track
		return nil
	}

	log.Info().Str("type", notification.NotificationType).Uint("user_id", user.ID).Str("product", transaction.ProductID).Msg("Apple Webhook: Processing notification")

	switch notification.NotificationType {
	case "SUBSCRIBED":
		// New subscription or resubscription
		return h.handleAppleSubscribed(user.ID, transaction, renewal)

	case "DID_RENEW":
		// Subscription successfully renewed
		return h.handleAppleRenewed(user.ID, transaction, renewal)

	case "DID_CHANGE_RENEWAL_STATUS":
		// User turned auto-renew on/off
		return h.handleAppleRenewalStatusChange(user.ID, transaction, renewal)

	case "DID_FAIL_TO_RENEW":
		// Billing issue - subscription may still be in grace period
		return h.handleAppleFailedToRenew(user.ID, transaction, renewal)

	case "EXPIRED":
		// Subscription expired
		return h.handleAppleExpired(user.ID, transaction)

	case "REFUND":
		// User got a refund
		return h.handleAppleRefund(user.ID, transaction)

	case "REVOKE":
		// Family sharing revoked or refund
		return h.handleAppleRevoke(user.ID, transaction)

	case "GRACE_PERIOD_EXPIRED":
		// Grace period ended without successful billing
		return h.handleAppleGracePeriodExpired(user.ID, transaction)

	default:
		log.Warn().Str("type", notification.NotificationType).Msg("Apple Webhook: Unhandled notification type")
	}

	return nil
}

func (h *SubscriptionWebhookHandler) findUserByAppleTransaction(originalTransactionID string) (*models.User, error) {
	// Look up user subscription by stored receipt data
	subscription, err := h.subscriptionRepo.FindByAppleReceiptContains(originalTransactionID)
	if err != nil {
		return nil, err
	}

	user, err := h.userRepo.FindByID(subscription.UserID)
	if err != nil {
		return nil, err
	}

	return user, nil
}

func (h *SubscriptionWebhookHandler) handleAppleSubscribed(userID uint, tx *AppleTransactionInfo, renewal *AppleRenewalInfo) error {
	expiresAt := time.Unix(tx.ExpiresDate/1000, 0)
	autoRenew := renewal != nil && renewal.AutoRenewStatus == 1

	if err := h.subscriptionRepo.UpgradeToPro(userID, expiresAt, "ios"); err != nil {
		return err
	}
	if err := h.subscriptionRepo.SetAutoRenew(userID, autoRenew); err != nil {
		return err
	}

	log.Info().Uint("user_id", userID).Time("expires", expiresAt).Bool("auto_renew", autoRenew).Msg("Apple Webhook: User subscribed")
	return nil
}

func (h *SubscriptionWebhookHandler) handleAppleRenewed(userID uint, tx *AppleTransactionInfo, renewal *AppleRenewalInfo) error {
	expiresAt := time.Unix(tx.ExpiresDate/1000, 0)

	if err := h.subscriptionRepo.UpgradeToPro(userID, expiresAt, "ios"); err != nil {
		return err
	}

	log.Info().Uint("user_id", userID).Time("expires", expiresAt).Msg("Apple Webhook: User renewed")
	return nil
}

func (h *SubscriptionWebhookHandler) handleAppleRenewalStatusChange(userID uint, tx *AppleTransactionInfo, renewal *AppleRenewalInfo) error {
	if renewal == nil {
		return nil
	}

	autoRenew := renewal.AutoRenewStatus == 1
	if err := h.subscriptionRepo.SetAutoRenew(userID, autoRenew); err != nil {
		return err
	}

	if !autoRenew {
		// User turned off auto-renew (will cancel at end of period)
		now := time.Now().UTC()
		if err := h.subscriptionRepo.SetCancelledAt(userID, now); err != nil {
			return err
		}
		log.Info().Uint("user_id", userID).Msg("Apple Webhook: User turned off auto-renew, will expire at end of period")
	} else {
		// User turned auto-renew back on
		if err := h.subscriptionRepo.ClearCancelledAt(userID); err != nil {
			return err
		}
		log.Info().Uint("user_id", userID).Msg("Apple Webhook: User turned auto-renew back on")
	}

	return nil
}

func (h *SubscriptionWebhookHandler) handleAppleFailedToRenew(userID uint, tx *AppleTransactionInfo, renewal *AppleRenewalInfo) error {
	// Subscription is in billing retry or grace period
	log.Warn().Uint("user_id", userID).Msg("Apple Webhook: User failed to renew, may be in grace period")
	// Don't downgrade yet - Apple may retry billing
	return nil
}

func (h *SubscriptionWebhookHandler) handleAppleExpired(userID uint, tx *AppleTransactionInfo) error {
	if err := h.safeDowngradeToFree(userID, "Apple expired"); err != nil {
		return err
	}
	return nil
}

func (h *SubscriptionWebhookHandler) handleAppleRefund(userID uint, tx *AppleTransactionInfo) error {
	if err := h.safeDowngradeToFree(userID, "Apple refund"); err != nil {
		return err
	}
	return nil
}

func (h *SubscriptionWebhookHandler) handleAppleRevoke(userID uint, tx *AppleTransactionInfo) error {
	if err := h.safeDowngradeToFree(userID, "Apple revoke"); err != nil {
		return err
	}
	return nil
}

func (h *SubscriptionWebhookHandler) handleAppleGracePeriodExpired(userID uint, tx *AppleTransactionInfo) error {
	if err := h.safeDowngradeToFree(userID, "Apple grace period expired"); err != nil {
		return err
	}
	return nil
}

// ====================
// Google Real-time Developer Notifications
// ====================

// GoogleNotification represents a Google Pub/Sub push message
type GoogleNotification struct {
	Message      GooglePubSubMessage `json:"message"`
	Subscription string              `json:"subscription"`
}

// GooglePubSubMessage represents the Pub/Sub message wrapper
type GooglePubSubMessage struct {
	Data        string            `json:"data"` // Base64 encoded
	MessageID   string            `json:"messageId"`
	PublishTime string            `json:"publishTime"`
	Attributes  map[string]string `json:"attributes"`
}

// GoogleDeveloperNotification represents the decoded notification
type GoogleDeveloperNotification struct {
	Version                  string                          `json:"version"`
	PackageName              string                          `json:"packageName"`
	EventTimeMillis          string                          `json:"eventTimeMillis"`
	SubscriptionNotification *GoogleSubscriptionNotification `json:"subscriptionNotification"`
	OneTimeProductNotification *GoogleOneTimeNotification    `json:"oneTimeProductNotification"`
	TestNotification         *GoogleTestNotification         `json:"testNotification"`
}

// GoogleSubscriptionNotification represents subscription-specific data
type GoogleSubscriptionNotification struct {
	Version          string `json:"version"`
	NotificationType int    `json:"notificationType"`
	PurchaseToken    string `json:"purchaseToken"`
	SubscriptionID   string `json:"subscriptionId"`
}

// GoogleOneTimeNotification represents one-time purchase data
type GoogleOneTimeNotification struct {
	Version          string `json:"version"`
	NotificationType int    `json:"notificationType"`
	PurchaseToken    string `json:"purchaseToken"`
	SKU              string `json:"sku"`
}

// GoogleTestNotification represents a test notification
type GoogleTestNotification struct {
	Version string `json:"version"`
}

// Google subscription notification types
const (
	GoogleSubRecovered            = 1  // Subscription recovered from account hold
	GoogleSubRenewed              = 2  // Active subscription renewed
	GoogleSubCanceled             = 3  // Subscription was cancelled (voluntary or involuntary)
	GoogleSubPurchased            = 4  // New subscription purchased
	GoogleSubOnHold               = 5  // Subscription entered account hold
	GoogleSubInGracePeriod        = 6  // Subscription entered grace period
	GoogleSubRestarted            = 7  // User reactivated subscription
	GoogleSubPriceChangeConfirmed = 8  // Price change confirmed by user
	GoogleSubDeferred             = 9  // Subscription deferred
	GoogleSubPaused               = 10 // Subscription paused
	GoogleSubPauseScheduleChanged = 11 // Pause schedule changed
	GoogleSubRevoked              = 12 // Subscription revoked
	GoogleSubExpired              = 13 // Subscription expired
)

// HandleGoogleWebhook handles POST /api/subscription/webhook/google/
func (h *SubscriptionWebhookHandler) HandleGoogleWebhook(c echo.Context) error {
	if !h.enabled {
		log.Info().Msg("Google Webhook: webhooks disabled by feature flag")
		return c.JSON(http.StatusOK, map[string]interface{}{"status": "webhooks_disabled"})
	}

	// C-01: Verify the Google Pub/Sub push authentication token before processing
	if !h.VerifyGooglePubSubToken(c) {
		return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "unauthorized"})
	}

	body, err := io.ReadAll(io.LimitReader(c.Request().Body, maxWebhookBodySize))
	if err != nil {
		log.Error().Err(err).Msg("Google Webhook: Failed to read body")
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "failed to read request body"})
	}

	var notification GoogleNotification
	if err := json.Unmarshal(body, &notification); err != nil {
		log.Error().Err(err).Msg("Google Webhook: Failed to parse notification")
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "invalid notification"})
	}

	// Decode the base64 data
	data, err := base64.StdEncoding.DecodeString(notification.Message.Data)
	if err != nil {
		log.Error().Err(err).Msg("Google Webhook: Failed to decode message data")
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "invalid message data"})
	}

	var devNotification GoogleDeveloperNotification
	if err := json.Unmarshal(data, &devNotification); err != nil {
		log.Error().Err(err).Msg("Google Webhook: Failed to parse developer notification")
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "invalid developer notification"})
	}

	// Dedup check using messageId
	messageID := notification.Message.MessageID
	if messageID != "" {
		alreadyProcessed, err := h.webhookEventRepo.HasProcessed("google", messageID)
		if err != nil {
			log.Error().Err(err).Msg("Google Webhook: Failed to check dedup")
			// Continue processing on dedup check failure (fail-open)
		} else if alreadyProcessed {
			log.Info().Str("message_id", messageID).Msg("Google Webhook: Duplicate event, skipping")
			return c.JSON(http.StatusOK, map[string]interface{}{"status": "duplicate"})
		}
	}

	// Handle test notification
	if devNotification.TestNotification != nil {
		log.Info().Msg("Google Webhook: Received test notification")
		return c.JSON(http.StatusOK, map[string]interface{}{"status": "test received"})
	}

	// Verify package name
	cfg := config.Get()
	if cfg != nil && cfg.GoogleIAP.PackageName != "" {
		if devNotification.PackageName != cfg.GoogleIAP.PackageName {
			log.Warn().Str("got", devNotification.PackageName).Str("expected", cfg.GoogleIAP.PackageName).Msg("Google Webhook: Package name mismatch")
			return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "package name mismatch"})
		}
	}

	// Process subscription notification
	if devNotification.SubscriptionNotification != nil {
		if err := h.processGoogleSubscriptionNotification(devNotification.SubscriptionNotification); err != nil {
			log.Error().Err(err).Msg("Google Webhook: Failed to process notification")
			// Still return 200 to acknowledge
		}
	}

	// Record processed event for dedup
	if messageID != "" {
		eventType := "unknown"
		if devNotification.SubscriptionNotification != nil {
			eventType = fmt.Sprintf("subscription_%d", devNotification.SubscriptionNotification.NotificationType)
		}
		if err := h.webhookEventRepo.RecordEvent("google", messageID, eventType, ""); err != nil {
			log.Error().Err(err).Msg("Google Webhook: Failed to record event")
		}
	}

	// Acknowledge the message
	return c.JSON(http.StatusOK, map[string]interface{}{"status": "received"})
}

// processGoogleSubscriptionNotification handles Google subscription events
func (h *SubscriptionWebhookHandler) processGoogleSubscriptionNotification(notification *GoogleSubscriptionNotification) error {
	// Find user by purchase token
	user, err := h.findUserByGoogleToken(notification.PurchaseToken)
	if err != nil {
		log.Warn().Err(err).Msg("Google Webhook: Could not find user for token")
		return nil // Not an error - might be unknown token
	}

	log.Info().Int("type", notification.NotificationType).Uint("user_id", user.ID).Str("subscription", notification.SubscriptionID).Msg("Google Webhook: Processing notification")

	switch notification.NotificationType {
	case GoogleSubPurchased:
		return h.handleGooglePurchased(user.ID, notification)

	case GoogleSubRenewed:
		return h.handleGoogleRenewed(user.ID, notification)

	case GoogleSubRecovered:
		return h.handleGoogleRecovered(user.ID, notification)

	case GoogleSubCanceled:
		return h.handleGoogleCanceled(user.ID, notification)

	case GoogleSubOnHold:
		return h.handleGoogleOnHold(user.ID, notification)

	case GoogleSubInGracePeriod:
		return h.handleGoogleGracePeriod(user.ID, notification)

	case GoogleSubRestarted:
		return h.handleGoogleRestarted(user.ID, notification)

	case GoogleSubRevoked:
		return h.handleGoogleRevoked(user.ID, notification)

	case GoogleSubExpired:
		return h.handleGoogleExpired(user.ID, notification)

	case GoogleSubPaused:
		return h.handleGooglePaused(user.ID, notification)

	default:
		log.Warn().Int("type", notification.NotificationType).Msg("Google Webhook: Unhandled notification type")
	}

	return nil
}

func (h *SubscriptionWebhookHandler) findUserByGoogleToken(purchaseToken string) (*models.User, error) {
	subscription, err := h.subscriptionRepo.FindByGoogleToken(purchaseToken)
	if err != nil {
		return nil, err
	}

	user, err := h.userRepo.FindByID(subscription.UserID)
	if err != nil {
		return nil, err
	}

	return user, nil
}

func (h *SubscriptionWebhookHandler) handleGooglePurchased(userID uint, notification *GoogleSubscriptionNotification) error {
	// New subscription - we should have already processed this via the client
	// This is a backup notification
	log.Info().Uint("user_id", userID).Str("subscription", notification.SubscriptionID).Msg("Google Webhook: User purchased subscription")
	return nil
}

func (h *SubscriptionWebhookHandler) handleGoogleRenewed(userID uint, notification *GoogleSubscriptionNotification) error {
	// Need to query Google API for new expiry date
	// For now, extend by typical period (1 month for monthly, 1 year for yearly)
	var extension time.Duration
	if strings.Contains(notification.SubscriptionID, "monthly") {
		extension = 30 * 24 * time.Hour
	} else {
		extension = 365 * 24 * time.Hour
	}

	newExpiry := time.Now().UTC().Add(extension)
	if err := h.subscriptionRepo.UpgradeToPro(userID, newExpiry, "android"); err != nil {
		return err
	}

	log.Info().Uint("user_id", userID).Time("expires", newExpiry).Msg("Google Webhook: User renewed")
	return nil
}

func (h *SubscriptionWebhookHandler) handleGoogleRecovered(userID uint, notification *GoogleSubscriptionNotification) error {
	// Subscription recovered from account hold - reactivate
	newExpiry := time.Now().UTC().AddDate(0, 1, 0) // 1 month from now
	if err := h.subscriptionRepo.UpgradeToPro(userID, newExpiry, "android"); err != nil {
		return err
	}

	log.Info().Uint("user_id", userID).Msg("Google Webhook: User subscription recovered")
	return nil
}

func (h *SubscriptionWebhookHandler) handleGoogleCanceled(userID uint, notification *GoogleSubscriptionNotification) error {
	// User canceled - will expire at end of period
	now := time.Now().UTC()
	if err := h.subscriptionRepo.SetCancelledAt(userID, now); err != nil {
		return err
	}
	if err := h.subscriptionRepo.SetAutoRenew(userID, false); err != nil {
		return err
	}

	log.Info().Uint("user_id", userID).Msg("Google Webhook: User canceled, will expire at end of period")
	return nil
}

func (h *SubscriptionWebhookHandler) handleGoogleOnHold(userID uint, notification *GoogleSubscriptionNotification) error {
	// Account hold - payment issue, may recover
	log.Warn().Uint("user_id", userID).Msg("Google Webhook: User subscription on hold")
	return nil
}

func (h *SubscriptionWebhookHandler) handleGoogleGracePeriod(userID uint, notification *GoogleSubscriptionNotification) error {
	// In grace period - user still has access but billing failed
	log.Warn().Uint("user_id", userID).Msg("Google Webhook: User in grace period")
	return nil
}

func (h *SubscriptionWebhookHandler) handleGoogleRestarted(userID uint, notification *GoogleSubscriptionNotification) error {
	// User restarted subscription
	newExpiry := time.Now().UTC().AddDate(0, 1, 0)
	if err := h.subscriptionRepo.UpgradeToPro(userID, newExpiry, "android"); err != nil {
		return err
	}
	if err := h.subscriptionRepo.ClearCancelledAt(userID); err != nil {
		return err
	}
	if err := h.subscriptionRepo.SetAutoRenew(userID, true); err != nil {
		return err
	}

	log.Info().Uint("user_id", userID).Msg("Google Webhook: User restarted subscription")
	return nil
}

func (h *SubscriptionWebhookHandler) handleGoogleRevoked(userID uint, notification *GoogleSubscriptionNotification) error {
	if err := h.safeDowngradeToFree(userID, "Google revoke"); err != nil {
		return err
	}
	return nil
}

func (h *SubscriptionWebhookHandler) handleGoogleExpired(userID uint, notification *GoogleSubscriptionNotification) error {
	if err := h.safeDowngradeToFree(userID, "Google expired"); err != nil {
		return err
	}
	return nil
}

func (h *SubscriptionWebhookHandler) handleGooglePaused(userID uint, notification *GoogleSubscriptionNotification) error {
	// Subscription paused by user
	log.Info().Uint("user_id", userID).Msg("Google Webhook: User subscription paused")
	return nil
}

// ====================
// Multi-Source Downgrade Safety
// ====================

// safeDowngradeToFree checks if the user has active subscriptions from other sources
// before downgrading to free. If another source is still active, skip the downgrade.
func (h *SubscriptionWebhookHandler) safeDowngradeToFree(userID uint, reason string) error {
	sub, err := h.subscriptionRepo.FindByUserID(userID)
	if err != nil {
		log.Warn().Err(err).Uint("user_id", userID).Str("reason", reason).Msg("Webhook: Could not find subscription for multi-source check, proceeding with downgrade")
		return h.subscriptionRepo.DowngradeToFree(userID)
	}

	// Check if Stripe subscription is still active
	if sub.HasStripeSubscription() && sub.Platform != models.PlatformStripe {
		log.Info().Uint("user_id", userID).Str("reason", reason).Msg("Webhook: Skipping downgrade — user has active Stripe subscription")
		return nil
	}
	// Check if Apple subscription is still active (for Google/Stripe webhooks)
	if sub.HasAppleSubscription() && sub.Platform != models.PlatformIOS {
		log.Info().Uint("user_id", userID).Str("reason", reason).Msg("Webhook: Skipping downgrade — user has active Apple subscription")
		return nil
	}
	// Check if Google subscription is still active (for Apple/Stripe webhooks)
	if sub.HasGoogleSubscription() && sub.Platform != models.PlatformAndroid {
		log.Info().Uint("user_id", userID).Str("reason", reason).Msg("Webhook: Skipping downgrade — user has active Google subscription")
		return nil
	}
	// Check if trial is still active
	if sub.IsTrialActive() {
		log.Info().Uint("user_id", userID).Str("reason", reason).Msg("Webhook: Skipping downgrade — user has active trial")
		return nil
	}

	if err := h.subscriptionRepo.DowngradeToFree(userID); err != nil {
		return err
	}

	log.Info().Uint("user_id", userID).Str("reason", reason).Msg("Webhook: User downgraded to free (no other active sources)")
	return nil
}

// ====================
// Stripe Webhooks
// ====================

// HandleStripeWebhook handles POST /api/subscription/webhook/stripe/
func (h *SubscriptionWebhookHandler) HandleStripeWebhook(c echo.Context) error {
	if !h.enabled {
		log.Info().Msg("Stripe Webhook: webhooks disabled by feature flag")
		return c.JSON(http.StatusOK, map[string]interface{}{"status": "webhooks_disabled"})
	}

	if h.stripeService == nil {
		log.Warn().Msg("Stripe Webhook: Stripe service not configured")
		return c.JSON(http.StatusOK, map[string]interface{}{"status": "not_configured"})
	}

	body, err := io.ReadAll(io.LimitReader(c.Request().Body, maxWebhookBodySize))
	if err != nil {
		log.Error().Err(err).Msg("Stripe Webhook: Failed to read body")
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "failed to read request body"})
	}

	signature := c.Request().Header.Get("Stripe-Signature")
	if signature == "" {
		log.Warn().Msg("Stripe Webhook: Missing Stripe-Signature header")
		return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "missing signature"})
	}

	if err := h.stripeService.HandleWebhookEvent(body, signature); err != nil {
		log.Error().Err(err).Msg("Stripe Webhook: Failed to process webhook")
		// Still return 200 to prevent Stripe from retrying on business logic errors
		// Only return error for signature verification failures
		if strings.Contains(err.Error(), "signature") {
			return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "invalid signature"})
		}
	}

	return c.JSON(http.StatusOK, map[string]interface{}{"status": "received"})
}

// ====================
// Signature Verification (Optional but Recommended)
// ====================

// VerifyAppleSignature verifies the JWS signature using Apple's root certificate.
// If root certificates are not loaded, verification fails (deny by default).
func (h *SubscriptionWebhookHandler) VerifyAppleSignature(signedPayload string) error {
	// Deny by default when root certificates are not loaded.
	if h.appleRootCerts == nil {
		return fmt.Errorf("Apple root certificates not configured: cannot verify JWS signature")
	}

	// Build a certificate pool from the loaded Apple root certificates
	rootPool := x509.NewCertPool()
	for _, cert := range h.appleRootCerts {
		rootPool.AddCert(cert)
	}

	// Parse the JWS token and verify the signature using the x5c certificate chain
	token, err := jwt.Parse(signedPayload, func(token *jwt.Token) (interface{}, error) {
		// Get the x5c header (certificate chain)
		x5c, ok := token.Header["x5c"].([]interface{})
		if !ok || len(x5c) == 0 {
			return nil, fmt.Errorf("missing x5c header")
		}

		// Decode the leaf certificate
		certData, err := base64.StdEncoding.DecodeString(x5c[0].(string))
		if err != nil {
			return nil, fmt.Errorf("failed to decode certificate: %w", err)
		}

		leafCert, err := x509.ParseCertificate(certData)
		if err != nil {
			return nil, fmt.Errorf("failed to parse certificate: %w", err)
		}

		// Build intermediate pool from remaining x5c entries
		intermediatePool := x509.NewCertPool()
		for i := 1; i < len(x5c); i++ {
			intermData, err := base64.StdEncoding.DecodeString(x5c[i].(string))
			if err != nil {
				return nil, fmt.Errorf("failed to decode intermediate certificate: %w", err)
			}
			intermCert, err := x509.ParseCertificate(intermData)
			if err != nil {
				return nil, fmt.Errorf("failed to parse intermediate certificate: %w", err)
			}
			intermediatePool.AddCert(intermCert)
		}

		// Verify the certificate chain against Apple's root certificates
		opts := x509.VerifyOptions{
			Roots:         rootPool,
			Intermediates: intermediatePool,
		}
		if _, err := leafCert.Verify(opts); err != nil {
			return nil, fmt.Errorf("certificate chain verification failed: %w", err)
		}

		ecdsaKey, ok := leafCert.PublicKey.(*ecdsa.PublicKey)
		if !ok {
			return nil, fmt.Errorf("leaf certificate public key is not ECDSA")
		}

		return ecdsaKey, nil
	})

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

	if !token.Valid {
		return fmt.Errorf("invalid token")
	}

	return nil
}

// googleOIDCCertsURL is the endpoint that serves Google's public OAuth2
// certificates used to verify JWTs issued by accounts.google.com (including
// Pub/Sub push tokens).
const googleOIDCCertsURL = "https://www.googleapis.com/oauth2/v3/certs"

// googleJWKSCache caches the fetched Google JWKS keys so we don't hit the
// network on every webhook request. Keys are refreshed when the cache expires.
var (
	googleJWKSCache     map[string]*rsa.PublicKey
	googleJWKSCacheMu   sync.RWMutex
	googleJWKSCacheTime time.Time
	googleJWKSCacheTTL  = 1 * time.Hour
)

// googleJWKSResponse represents the JSON Web Key Set response from Google.
type googleJWKSResponse struct {
	Keys []googleJWK `json:"keys"`
}

// googleJWK represents a single JSON Web Key from Google's OIDC endpoint.
type googleJWK struct {
	Kid string `json:"kid"` // Key ID
	Kty string `json:"kty"` // Key type (RSA)
	Alg string `json:"alg"` // Algorithm (RS256)
	N   string `json:"n"`   // RSA modulus (base64url)
	E   string `json:"e"`   // RSA exponent (base64url)
}

// fetchGoogleOIDCKeys fetches Google's public OIDC keys from their well-known
// endpoint, returning a map of key-id to RSA public key. Results are cached
// for googleJWKSCacheTTL to avoid excessive network calls.
func fetchGoogleOIDCKeys() (map[string]*rsa.PublicKey, error) {
	googleJWKSCacheMu.RLock()
	if googleJWKSCache != nil && time.Since(googleJWKSCacheTime) < googleJWKSCacheTTL {
		cached := googleJWKSCache
		googleJWKSCacheMu.RUnlock()
		return cached, nil
	}
	googleJWKSCacheMu.RUnlock()

	googleJWKSCacheMu.Lock()
	defer googleJWKSCacheMu.Unlock()

	// Double-check after acquiring write lock
	if googleJWKSCache != nil && time.Since(googleJWKSCacheTime) < googleJWKSCacheTTL {
		return googleJWKSCache, nil
	}

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Get(googleOIDCCertsURL)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch Google OIDC keys: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("Google OIDC keys endpoint returned status %d", resp.StatusCode)
	}

	var jwks googleJWKSResponse
	if err := json.NewDecoder(io.LimitReader(resp.Body, maxWebhookBodySize)).Decode(&jwks); err != nil {
		return nil, fmt.Errorf("failed to decode Google OIDC keys: %w", err)
	}

	keys := make(map[string]*rsa.PublicKey, len(jwks.Keys))
	for _, k := range jwks.Keys {
		if k.Kty != "RSA" {
			continue
		}
		nBytes, err := base64.RawURLEncoding.DecodeString(k.N)
		if err != nil {
			log.Warn().Str("kid", k.Kid).Err(err).Msg("Google OIDC: failed to decode modulus")
			continue
		}
		eBytes, err := base64.RawURLEncoding.DecodeString(k.E)
		if err != nil {
			log.Warn().Str("kid", k.Kid).Err(err).Msg("Google OIDC: failed to decode exponent")
			continue
		}

		n := new(big.Int).SetBytes(nBytes)
		e := 0
		for _, b := range eBytes {
			e = e<<8 + int(b)
		}

		keys[k.Kid] = &rsa.PublicKey{N: n, E: e}
	}

	if len(keys) == 0 {
		return nil, fmt.Errorf("no usable RSA keys found in Google OIDC response")
	}

	googleJWKSCache = keys
	googleJWKSCacheTime = time.Now()
	return keys, nil
}

// VerifyGooglePubSubToken verifies the Pub/Sub push authentication token.
// The token is a JWT signed by Google (accounts.google.com). This function
// verifies the signature against Google's published OIDC public keys, checks
// the issuer claim, and validates the email claim is a Google service account.
// Returns false (deny) when verification fails for any reason.
func (h *SubscriptionWebhookHandler) VerifyGooglePubSubToken(c echo.Context) bool {
	authHeader := c.Request().Header.Get("Authorization")
	if authHeader == "" {
		log.Warn().Msg("Google Webhook: missing Authorization header")
		return false
	}

	// Expect "Bearer <token>" format
	if !strings.HasPrefix(authHeader, "Bearer ") {
		log.Warn().Msg("Google Webhook: Authorization header is not Bearer token")
		return false
	}

	bearerToken := strings.TrimPrefix(authHeader, "Bearer ")
	if bearerToken == "" {
		log.Warn().Msg("Google Webhook: empty Bearer token")
		return false
	}

	// Fetch Google's OIDC public keys for signature verification
	googleKeys, err := fetchGoogleOIDCKeys()
	if err != nil {
		log.Error().Err(err).Msg("Google Webhook: failed to fetch OIDC keys, denying request")
		return false
	}

	// Parse and verify the JWT signature against Google's published public keys
	token, err := jwt.Parse(bearerToken, func(token *jwt.Token) (interface{}, error) {
		// Ensure the signing method is RSA (Google uses RS256)
		if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}

		kid, _ := token.Header["kid"].(string)
		if kid == "" {
			return nil, fmt.Errorf("missing kid header in token")
		}

		key, ok := googleKeys[kid]
		if !ok {
			// Key may have rotated; try refreshing once
			googleJWKSCacheMu.Lock()
			googleJWKSCacheTime = time.Time{} // Force refresh
			googleJWKSCacheMu.Unlock()

			refreshedKeys, err := fetchGoogleOIDCKeys()
			if err != nil {
				return nil, fmt.Errorf("failed to refresh Google OIDC keys: %w", err)
			}
			key, ok = refreshedKeys[kid]
			if !ok {
				return nil, fmt.Errorf("unknown key ID: %s", kid)
			}
		}

		return key, nil
	}, jwt.WithValidMethods([]string{"RS256"}))

	if err != nil {
		log.Warn().Err(err).Msg("Google Webhook: JWT signature verification failed")
		return false
	}

	if !token.Valid {
		log.Warn().Msg("Google Webhook: token is invalid after verification")
		return false
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		log.Warn().Msg("Google Webhook: invalid token claims")
		return false
	}

	// Verify issuer is Google
	issuer, _ := claims.GetIssuer()
	if issuer != "accounts.google.com" && issuer != "https://accounts.google.com" {
		log.Warn().Str("issuer", issuer).Msg("Google Webhook: unexpected issuer")
		return false
	}

	// Verify the email claim matches a Google service account
	email, _ := claims["email"].(string)
	if email == "" || !strings.HasSuffix(email, ".gserviceaccount.com") {
		log.Warn().Str("email", email).Msg("Google Webhook: token email is not a Google service account")
		return false
	}

	return true
}

// Helper function to load Apple root certificates from file
func loadAppleRootCertificates(certPath string) ([]*x509.Certificate, error) {
	data, err := os.ReadFile(certPath)
	if err != nil {
		return nil, err
	}

	var certs []*x509.Certificate
	for {
		block, rest := pem.Decode(data)
		if block == nil {
			break
		}
		if block.Type == "CERTIFICATE" {
			cert, err := x509.ParseCertificate(block.Bytes)
			if err != nil {
				return nil, err
			}
			certs = append(certs, cert)
		}
		data = rest
	}

	return certs, nil
}
