package httpx

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/stripe/stripe-go/v83"
	stripewebhook "github.com/stripe/stripe-go/v83/webhook"

	"royal-pop-backend/internal/config"
	"royal-pop-backend/internal/mailer"
	"royal-pop-backend/internal/orders"
)

const maxStripeWebhookBodyBytes int64 = 64 * 1024

type stripeWebhookResult struct {
	EventID         string `json:"eventId"`
	EventType       string `json:"eventType"`
	PaymentIntentID string `json:"paymentIntentId,omitempty"`
	OrderState      string `json:"orderState"`
	Persistence     string `json:"persistence"`
	Message         string `json:"message"`
	Verified        bool   `json:"verified"`
}

func handleStripeWebhook(cfg *config.Config, orderStore *orders.Store, fulfillmentMailer *mailer.ResendMailer) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if cfg.StripeWebhookSecret == "" {
			WriteError(w, http.StatusInternalServerError, "stripe_webhook_not_configured", "Missing STRIPE_WEBHOOK_SECRET for Stripe webhook verification.")
			return
		}

		r.Body = http.MaxBytesReader(w, r.Body, maxStripeWebhookBodyBytes)
		payload, err := io.ReadAll(r.Body)
		if err != nil {
			WriteError(w, http.StatusBadRequest, "invalid_webhook_payload", "The Stripe webhook payload could not be read.")
			return
		}

		signature := r.Header.Get("Stripe-Signature")
		if strings.TrimSpace(signature) == "" {
			WriteError(w, http.StatusBadRequest, "missing_stripe_signature", "Missing Stripe-Signature header.")
			return
		}

		constructOptions := stripewebhook.ConstructEventOptions{}
		constructOptions.IgnoreAPIVersionMismatch = true

		event, err := stripewebhook.ConstructEventWithOptions(payload, signature, cfg.StripeWebhookSecret, constructOptions)
		if err != nil {
			status := http.StatusBadRequest
			if errors.Is(err, stripewebhook.ErrTooOld) {
				status = http.StatusUnauthorized
			}
			log.Printf("stripe webhook rejected: status=%d error=%v", status, err)
			WriteError(w, status, "invalid_stripe_signature", err.Error())
			return
		}

		result, err := processStripeWebhookEvent(event)
		if err != nil {
			WriteError(w, http.StatusBadRequest, "unsupported_webhook_payload", err.Error())
			return
		}

		paymentIntentStates.applyWebhook(result)

		if orderStore != nil && result.PaymentIntentID != "" {
			updateCtx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
			defer cancel()

			previousOrder, err := orderStore.GetByPaymentIntentID(updateCtx, result.PaymentIntentID)
			if err != nil && !errors.Is(err, orders.ErrOrderNotFound) {
				WriteError(w, http.StatusInternalServerError, "order_lookup_failed", "Stripe webhook verification succeeded, but the current order record could not be loaded.")
				return
			}

			if err := orderStore.UpdateFromWebhook(updateCtx, result.PaymentIntentID, orders.WebhookUpdate{
				Status:           orderStatusForWebhookResult(result.OrderState),
				WebhookStatus:    webhookStatusForWebhookResult(result.OrderState),
				WebhookEventID:   result.EventID,
				WebhookEventType: result.EventType,
				WebhookMessage:   result.Message,
			}); err != nil && !errors.Is(err, orders.ErrOrderNotFound) {
				WriteError(w, http.StatusInternalServerError, "order_webhook_update_failed", "Stripe webhook verification succeeded, but the order record could not be updated.")
				return
			}

			if fulfillmentMailer != nil && fulfillmentMailer.Enabled() {
				updatedOrder, err := orderStore.GetByPaymentIntentID(updateCtx, result.PaymentIntentID)
				if err != nil && !errors.Is(err, orders.ErrOrderNotFound) {
					WriteError(w, http.StatusInternalServerError, "order_reload_failed", "Stripe webhook verification succeeded, but the updated order record could not be loaded.")
					return
				}

				if shouldSendPaidEmail(previousOrder, updatedOrder) {
					if err := fulfillmentMailer.SendOrderPaid(updateCtx, updatedOrder); err != nil {
						log.Printf("paid email failed for order %s: %v", updatedOrder.ID, err)
					}

					if err := fulfillmentMailer.SendOrderCreated(updateCtx, updatedOrder); err != nil {
						log.Printf("order created reminder email failed for order %s: %v", updatedOrder.ID, err)
					}
				}
			}
		}

		log.Printf("stripe webhook received: event_id=%s type=%s payment_intent=%s order_state=%s", result.EventID, result.EventType, result.PaymentIntentID, result.OrderState)

		WriteJSON(w, http.StatusOK, map[string]any{
			"received": true,
			"data":     result,
		})
	}
}

func shouldSendPaidEmail(previousOrder, updatedOrder *orders.Order) bool {
	if previousOrder == nil || updatedOrder == nil {
		return false
	}

	if updatedOrder.Status != orders.StatusPaid {
		return false
	}

	if previousOrder.Status == orders.StatusPaid {
		return false
	}

	return strings.TrimSpace(updatedOrder.Email) != ""
}

func processStripeWebhookEvent(event stripe.Event) (*stripeWebhookResult, error) {
	intent, err := decodePaymentIntentFromEvent(event)
	if err != nil {
		return nil, err
	}

	result := &stripeWebhookResult{
		EventID:         event.ID,
		EventType:       string(event.Type),
		PaymentIntentID: intent.ID,
		Persistence:     "not_persisted_yet",
	}

	switch event.Type {
	case stripe.EventTypePaymentIntentSucceeded:
		result.OrderState = "paid_verified_local_only"
		result.Verified = true
		result.Message = "Stripe confirmed the payment and updated the order record."
	case stripe.EventTypePaymentIntentPaymentFailed:
		result.OrderState = "payment_failed_verified_local_only"
		result.Verified = true
		result.Message = buildPaymentIntentFailureMessage(intent)
	case stripe.EventTypePaymentIntentProcessing:
		result.OrderState = "processing_verified_local_only"
		result.Verified = true
		result.Message = "Stripe marked the payment intent as processing. The order record has been updated and is waiting for the next payment event."
	case stripe.EventTypePaymentIntentCanceled:
		result.OrderState = "canceled_verified_local_only"
		result.Verified = true
		result.Message = "Stripe marked the payment intent as canceled and the order record has been updated."
	default:
		result.OrderState = "ignored"
		result.Verified = false
		result.Message = fmt.Sprintf("Received Stripe event %s. Signature verified, but no persistence action is configured for this event yet.", event.Type)
	}

	return result, nil
}

func decodePaymentIntentFromEvent(event stripe.Event) (*stripe.PaymentIntent, error) {
	if len(event.Data.Raw) == 0 {
		return nil, fmt.Errorf("stripe event %s did not include an object payload", event.Type)
	}

	var intent stripe.PaymentIntent
	if err := json.Unmarshal(event.Data.Raw, &intent); err != nil {
		return nil, fmt.Errorf("stripe event %s could not be parsed as a payment intent: %w", event.Type, err)
	}

	return &intent, nil
}

func buildPaymentIntentFailureMessage(intent *stripe.PaymentIntent) string {
	if intent == nil || intent.LastPaymentError == nil {
		return "Stripe reported that the payment intent failed and the order record has been updated."
	}

	message := strings.TrimSpace(intent.LastPaymentError.Msg)
	if message == "" {
		return "Stripe reported that the payment intent failed and the order record has been updated."
	}

	return fmt.Sprintf("Stripe reported that the payment intent failed: %s", message)
}

func orderStatusForWebhookResult(orderState string) string {
	switch orderState {
	case "paid_verified_local_only", "paid_unrecorded":
		return orders.StatusPaid
	case "payment_failed_verified_local_only", "payment_failed_unrecorded":
		return orders.StatusFailed
	case "processing_verified_local_only", "processing_unrecorded":
		return orders.StatusProcessing
	case "canceled_verified_local_only", "canceled_unrecorded":
		return orders.StatusCanceled
	default:
		return orders.StatusPending
	}
}

func webhookStatusForWebhookResult(orderState string) string {
	switch orderState {
	case "paid_verified_local_only", "paid_unrecorded":
		return "succeeded"
	case "payment_failed_verified_local_only", "payment_failed_unrecorded":
		return "failed"
	case "processing_verified_local_only", "processing_unrecorded":
		return "processing"
	case "canceled_verified_local_only", "canceled_unrecorded":
		return "canceled"
	case "ignored":
		return "ignored"
	default:
		return "awaiting_webhook"
	}
}
