package httpx

import (
	"encoding/json"
	"fmt"
	"net/http"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/go-chi/chi/v5"
	"github.com/stripe/stripe-go/v83"
	"github.com/stripe/stripe-go/v83/paymentintent"
	stripeprice "github.com/stripe/stripe-go/v83/price"

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

type createPaymentIntentRequest struct {
	Items         []checkoutItem          `json:"items"`
	PaymentMethod string                  `json:"paymentMethod"`
	Customer      checkoutCustomerDetails `json:"customer"`
}

type checkoutItem struct {
	ID         string `json:"id"`
	Style      string `json:"style"`
	ColorwayID string `json:"colorwayId"`
	FinishID   string `json:"finishId"`
	Quantity   int64  `json:"quantity,omitempty"`
}

type checkoutCustomerDetails struct {
	Email        string `json:"email"`
	Phone        string `json:"phone"`
	FirstName    string `json:"firstName"`
	LastName     string `json:"lastName"`
	AddressLine1 string `json:"addressLine1"`
	AddressLine2 string `json:"addressLine2"`
	City         string `json:"city"`
	Region       string `json:"region"`
	PostalCode   string `json:"postalCode"`
	Country      string `json:"country"`
	Notes        string `json:"notes"`
}

type storefrontPricing struct {
	PriceID      string
	Currency     string
	UnitAmount   int64
	RetailAmount int64
}

var emailPattern = regexp.MustCompile(`^[^\s@]+@[^\s@]+\.[^\s@]+$`)

func handleCreatePaymentIntent(cfg *config.Config, orderStore *orders.Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if cfg.StripeSecretKey == "" {
			WriteError(w, http.StatusInternalServerError, "stripe_not_configured", "Missing STRIPE_SECRET_KEY for backend Stripe calls.")
			return
		}

		var request createPaymentIntentRequest
		if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
			WriteError(w, http.StatusBadRequest, "invalid_request", "The payment intent payload could not be parsed.")
			return
		}

		itemCount := countItems(request.Items)
		if itemCount <= 0 {
			WriteError(w, http.StatusBadRequest, "empty_cart", "Add at least one Royal Pop kit before creating a payment intent.")
			return
		}

		customerDetails, err := validateCustomerDetails(request.Customer)
		if err != nil {
			WriteError(w, http.StatusBadRequest, "invalid_customer_details", err.Error())
			return
		}

		pricing := resolveStorefrontPricing(cfg)
		stripe.Key = cfg.StripeSecretKey
		amount := itemCount * pricing.UnitAmount
		orderID := orders.NewOrderID()
		metadata := buildPaymentIntentMetadata(cfg, request, itemCount, amount, orderID)

		params := &stripe.PaymentIntentParams{
			Amount:   stripe.Int64(amount),
			Currency: stripe.String(pricing.Currency),
			AutomaticPaymentMethods: &stripe.PaymentIntentAutomaticPaymentMethodsParams{
				Enabled:        stripe.Bool(true),
				AllowRedirects: stripe.String("never"),
			},
			Description: stripe.String(fmt.Sprintf("Royal Pop preorder (%d kit%s)", itemCount, pluralize(itemCount))),
		}

		for key, value := range metadata {
			params.AddMetadata(key, value)
		}

		intent, err := paymentintent.New(params)
		if err != nil {
			WriteError(w, http.StatusBadGateway, "stripe_payment_intent_failed", err.Error())
			return
		}

		orderRecord, err := orderStore.CreatePendingOrder(r.Context(), orders.CreateOrderInput{
			OrderID: orderID,
			Customer: orders.CustomerDetails{
				Email:        customerDetails.Email,
				Phone:        customerDetails.Phone,
				FirstName:    customerDetails.FirstName,
				LastName:     customerDetails.LastName,
				AddressLine1: customerDetails.AddressLine1,
				AddressLine2: customerDetails.AddressLine2,
				City:         customerDetails.City,
				Region:       customerDetails.Region,
				PostalCode:   customerDetails.PostalCode,
				Country:      customerDetails.Country,
				Notes:        customerDetails.Notes,
			},
			Items:                 buildOrderItems(request.Items, pricing.UnitAmount),
			Currency:              pricing.Currency,
			Amount:                amount,
			StripePriceID:         cfg.StripePriceID,
			StripePaymentIntentID: intent.ID,
		})
		if err != nil {
			WriteError(w, http.StatusInternalServerError, "order_persist_failed", "The payment session was created, but the order could not be saved. Please try again.")
			return
		}

		paymentIntentStates.upsertCreated(intent.ID)

		WriteJSON(w, http.StatusCreated, map[string]any{
			"data": map[string]any{
				"orderId":         orderRecord.ID,
				"paymentIntentId": intent.ID,
				"clientSecret":    intent.ClientSecret,
				"amount":          intent.Amount,
				"currency":        intent.Currency,
				"itemCount":       itemCount,
				"priceId":         cfg.StripePriceID,
				"unitAmount":      pricing.UnitAmount,
				"submitEnabled":   true,
				"message":         paymentIntentCreatedMessage(cfg),
			},
		})
	}
}

func handleGetStorefrontPricing(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, _ *http.Request) {
		pricing := resolveStorefrontPricing(cfg)

		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"priceId":      pricing.PriceID,
				"currency":     pricing.Currency,
				"unitAmount":   pricing.UnitAmount,
				"retailAmount": pricing.RetailAmount,
			},
		})
	}
}

func handleGetPaymentIntentStatus(cfg *config.Config, orderStore *orders.Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if cfg.StripeSecretKey == "" {
			WriteError(w, http.StatusInternalServerError, "stripe_not_configured", "Missing STRIPE_SECRET_KEY for backend Stripe calls.")
			return
		}

		paymentIntentID := strings.TrimSpace(chi.URLParam(r, "paymentIntentID"))
		if paymentIntentID == "" {
			WriteError(w, http.StatusBadRequest, "missing_payment_intent_id", "The payment intent ID is required.")
			return
		}

		stripe.Key = cfg.StripeSecretKey
		intent, err := paymentintent.Get(paymentIntentID, nil)
		if err != nil {
			WriteError(w, http.StatusBadGateway, "stripe_payment_intent_lookup_failed", err.Error())
			return
		}

		verification, exists := paymentIntentStates.get(intent.ID)
		orderRecord, orderErr := orderStore.GetByPaymentIntentID(r.Context(), intent.ID)
		if orderErr == nil {
			verification = paymentIntentVerification{
				PaymentIntentID:  orderRecord.StripePaymentIntentID,
				WebhookState:     orderRecord.WebhookStatus,
				WebhookEventID:   orderRecord.WebhookEventID,
				WebhookEventType: orderRecord.WebhookEventType,
				Message:          orderRecord.WebhookMessage,
				Verified:         orderRecord.Status == orders.StatusPaid,
			}
			exists = true
		}
		if !exists {
			verification = paymentIntentVerification{
				PaymentIntentID: intent.ID,
				WebhookState:    "awaiting_webhook",
				Message:         "Waiting for Stripe webhook verification.",
			}
		}

		message := verification.Message
		if message == "" {
			message = defaultStatusMessage(intent.Status, verification.Verified)
		}

		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"orderId":          orderIDFromOrder(orderRecord),
				"orderStatus":      orderStatusFromOrder(orderRecord),
				"order":            serializeOrder(orderRecord),
				"paymentIntentId":  intent.ID,
				"stripeStatus":     string(intent.Status),
				"amount":           intent.Amount,
				"currency":         intent.Currency,
				"webhookStatus":    verification.WebhookState,
				"webhookEventId":   verification.WebhookEventID,
				"webhookEventType": verification.WebhookEventType,
				"verified":         verification.Verified,
				"message":          message,
			},
		})
	}
}

func countItems(items []checkoutItem) int64 {
	var total int64
	for _, item := range items {
		quantity := item.Quantity
		if quantity <= 0 {
			quantity = 1
		}
		total += quantity
	}
	return total
}

func buildPaymentIntentMetadata(cfg *config.Config, request createPaymentIntentRequest, itemCount int64, amount int64, orderID string) map[string]string {
	pricing := resolveStorefrontPricing(cfg)
	colorways := make([]string, 0, len(request.Items))
	styles := make([]string, 0, len(request.Items))

	for _, item := range request.Items {
		if item.ColorwayID != "" {
			colorways = append(colorways, item.ColorwayID)
		}
		if item.Style != "" {
			styles = append(styles, item.Style)
		}
	}

	sort.Strings(colorways)
	sort.Strings(styles)

	return map[string]string{
		"order_id":          orderID,
		"source":            "royal-pop-storefront",
		"stripe_price_id":   cfg.StripePriceID,
		"payment_method_ui": strings.TrimSpace(request.PaymentMethod),
		"item_count":        fmt.Sprintf("%d", itemCount),
		"unit_amount":       fmt.Sprintf("%d", pricing.UnitAmount),
		"amount_total":      fmt.Sprintf("%d", amount),
		"colorways":         strings.Join(colorways, ","),
		"styles":            strings.Join(styles, ","),
	}
}

func resolveStorefrontPricing(cfg *config.Config) storefrontPricing {
	pricing := storefrontPricing{
		PriceID:      strings.TrimSpace(cfg.StripePriceID),
		Currency:     strings.ToLower(strings.TrimSpace(cfg.StripeCurrency)),
		UnitAmount:   cfg.RoyalPopUnitAmount,
		RetailAmount: cfg.RoyalPopRetailAmount,
	}

	if pricing.Currency == "" {
		pricing.Currency = "gbp"
	}

	if strings.TrimSpace(cfg.StripeSecretKey) == "" || pricing.PriceID == "" {
		return pricing
	}

	stripe.Key = cfg.StripeSecretKey
	priceRecord, err := stripeprice.Get(pricing.PriceID, nil)
	if err != nil || priceRecord == nil {
		return pricing
	}

	if priceRecord.UnitAmount > 0 {
		pricing.UnitAmount = priceRecord.UnitAmount
	}

	if currency := strings.ToLower(string(priceRecord.Currency)); currency != "" {
		pricing.Currency = currency
	}

	for _, key := range []string{"retail_amount", "compare_at_amount", "compareAtAmount"} {
		if raw := strings.TrimSpace(priceRecord.Metadata[key]); raw != "" {
			if parsed, err := strconv.ParseInt(raw, 10, 64); err == nil && parsed > 0 {
				pricing.RetailAmount = parsed
				break
			}
		}
	}

	return pricing
}

func buildOrderItems(items []checkoutItem, unitAmount int64) []orders.LineItem {
	result := make([]orders.LineItem, 0, len(items))
	for _, item := range items {
		quantity := item.Quantity
		if quantity <= 0 {
			quantity = 1
		}
		result = append(result, orders.LineItem{
			ItemID:     strings.TrimSpace(item.ID),
			Style:      strings.TrimSpace(item.Style),
			ColorwayID: strings.TrimSpace(item.ColorwayID),
			FinishID:   strings.TrimSpace(item.FinishID),
			Quantity:   quantity,
			UnitAmount: unitAmount,
		})
	}
	return result
}

func validateCustomerDetails(input checkoutCustomerDetails) (checkoutCustomerDetails, error) {
	normalized := checkoutCustomerDetails{
		Email:        strings.TrimSpace(input.Email),
		Phone:        strings.TrimSpace(input.Phone),
		FirstName:    strings.TrimSpace(input.FirstName),
		LastName:     strings.TrimSpace(input.LastName),
		AddressLine1: strings.TrimSpace(input.AddressLine1),
		AddressLine2: strings.TrimSpace(input.AddressLine2),
		City:         strings.TrimSpace(input.City),
		Region:       strings.TrimSpace(input.Region),
		PostalCode:   strings.TrimSpace(input.PostalCode),
		Country:      strings.TrimSpace(input.Country),
		Notes:        strings.TrimSpace(input.Notes),
	}

	if !emailPattern.MatchString(normalized.Email) {
		return normalized, fmt.Errorf("Enter a valid email address before continuing to payment.")
	}
	if len(normalized.Phone) < 7 {
		return normalized, fmt.Errorf("Enter a valid phone number before continuing to payment.")
	}
	if len(normalized.FirstName) < 2 || len(normalized.LastName) < 2 {
		return normalized, fmt.Errorf("Enter the full shipping name before continuing to payment.")
	}
	if len(normalized.AddressLine1) < 5 || len(normalized.City) < 2 || len(normalized.Region) < 2 || len(normalized.PostalCode) < 3 || len(normalized.Country) < 2 {
		return normalized, fmt.Errorf("Complete the full shipping address before continuing to payment.")
	}

	return normalized, nil
}

func orderIDFromOrder(order *orders.Order) string {
	if order == nil {
		return ""
	}

	return order.ID
}

func orderStatusFromOrder(order *orders.Order) string {
	if order == nil {
		return ""
	}

	return order.Status
}

func pluralize(value int64) string {
	if value == 1 {
		return ""
	}

	return "s"
}

func paymentIntentCreatedMessage(cfg *config.Config) string {
	if cfg.IsDevelopment() {
		return "Payment session created. Complete the payment form, then wait for Stripe webhook verification to confirm the order."
	}

	return "Payment session created. Complete the payment form, then wait for Stripe webhook verification to confirm the order."
}

func defaultStatusMessage(status stripe.PaymentIntentStatus, verified bool) string {
	if verified {
		return "Stripe confirmed your payment and updated the order record."
	}

	switch status {
	case stripe.PaymentIntentStatusSucceeded:
		return "Stripe reports this payment as succeeded, but the backend is still waiting for the webhook confirmation."
	case stripe.PaymentIntentStatusProcessing:
		return "Stripe is still processing this payment intent."
	case stripe.PaymentIntentStatusCanceled:
		return "Stripe marked this payment intent as canceled."
	case stripe.PaymentIntentStatusRequiresPaymentMethod:
		return "Stripe needs a valid payment method before this payment can complete."
	case stripe.PaymentIntentStatusRequiresAction:
		return "Stripe requires additional customer action before this payment can complete."
	default:
		return "Waiting for the next Stripe payment update."
	}
}

func serializeOrder(order *orders.Order) map[string]any {
	if order == nil {
		return nil
	}

	items := make([]map[string]any, 0, len(order.Items))
	for _, item := range order.Items {
		items = append(items, map[string]any{
			"itemId":     item.ItemID,
			"style":      item.Style,
			"colorwayId": item.ColorwayID,
			"finishId":   item.FinishID,
			"quantity":   item.Quantity,
			"unitAmount": item.UnitAmount,
		})
	}

	return map[string]any{
		"id":            order.ID,
		"status":        order.Status,
		"currency":      order.Currency,
		"amount":        order.Amount,
		"webhookStatus": order.WebhookStatus,
		"items":         items,
	}
}
