package handlers

import (
	"encoding/json"
	"io"
	"log"
	"net/http"

	"github.com/stripe/stripe-go/v82"
	portalsession "github.com/stripe/stripe-go/v82/billingportal/session"
	checkoutsession "github.com/stripe/stripe-go/v82/checkout/session"
	"github.com/stripe/stripe-go/v82/webhook"

	"github.com/kjannette/koin-ping/backend/internal/config"
	"github.com/kjannette/koin-ping/backend/internal/middleware"
	"github.com/kjannette/koin-ping/backend/internal/models"
)

const webhookMaxBodyBytes = 65536

type StripeHandler struct {
	users *models.UserModel
	cfg   *config.Config
}

func NewStripeHandler(users *models.UserModel, cfg *config.Config) *StripeHandler {
	stripe.Key = cfg.StripeSecretKey
	return &StripeHandler{users: users, cfg: cfg}
}

// CreateCheckoutSession creates a Stripe Checkout session for the monthly subscription.
func (h *StripeHandler) CreateCheckoutSession(w http.ResponseWriter, r *http.Request) {
	userID := middleware.GetUserID(r.Context())

	user, err := h.users.GetByID(r.Context(), userID)
	if err != nil || user == nil {
		log.Printf("Failed to get user %s: %v", userID, err)
		writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", "Failed to load user")
		return
	}

	params := &stripe.CheckoutSessionParams{
		Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)),
		LineItems: []*stripe.CheckoutSessionLineItemParams{
			{
				Price:    stripe.String(h.cfg.StripePriceID),
				Quantity: stripe.Int64(1),
			},
		},
		SuccessURL:        stripe.String(h.cfg.FrontendURL + "/subscribe?payment=success&session_id={CHECKOUT_SESSION_ID}"),
		CancelURL:         stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"),
		ClientReferenceID: stripe.String(userID),
		CustomerEmail:     stripe.String(user.Email),
	}

	if user.StripeCustomerID != nil && *user.StripeCustomerID != "" {
		params.Customer = user.StripeCustomerID
		params.CustomerEmail = nil
	}

	s, err := checkoutsession.New(params)
	if err != nil {
		log.Printf("Failed to create Stripe checkout session: %v", err)
		writeError(w, http.StatusInternalServerError, "STRIPE_ERROR", "Failed to create checkout session")
		return
	}

	writeJSON(w, http.StatusOK, map[string]string{"url": s.URL})
}

// GetSubscriptionStatus returns the current user's subscription state.
func (h *StripeHandler) GetSubscriptionStatus(w http.ResponseWriter, r *http.Request) {
	userID := middleware.GetUserID(r.Context())

	user, err := h.users.GetByID(r.Context(), userID)
	if err != nil || user == nil {
		log.Printf("Failed to get user %s: %v", userID, err)
		writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", "Failed to load user")
		return
	}

	writeJSON(w, http.StatusOK, map[string]any{
		"subscription_status":     user.SubscriptionStatus,
		"subscription_created_at": user.SubscriptionCreatedAt,
	})
}

// VerifyCheckoutSession retrieves a completed checkout session from Stripe,
// confirms payment, and activates the user's subscription in the database.
// This is the primary activation path; webhooks serve as a backup.
func (h *StripeHandler) VerifyCheckoutSession(w http.ResponseWriter, r *http.Request) {
	userID := middleware.GetUserID(r.Context())

	var body struct {
		SessionID string `json:"session_id"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.SessionID == "" {
		writeError(w, http.StatusBadRequest, "BAD_REQUEST", "Missing session_id")
		return
	}

	s, err := checkoutsession.Get(body.SessionID, nil)
	if err != nil {
		log.Printf("Failed to retrieve checkout session %s: %v", body.SessionID, err)
		writeError(w, http.StatusBadRequest, "STRIPE_ERROR", "Invalid checkout session")
		return
	}

	if s.ClientReferenceID != userID {
		writeError(w, http.StatusForbidden, "FORBIDDEN", "Session does not belong to this user")
		return
	}

	if s.PaymentStatus != stripe.CheckoutSessionPaymentStatusPaid {
		writeError(w, http.StatusBadRequest, "PAYMENT_INCOMPLETE", "Payment has not been completed")
		return
	}

	customerID := ""
	if s.Customer != nil {
		customerID = s.Customer.ID
	}
	subscriptionID := ""
	if s.Subscription != nil {
		subscriptionID = s.Subscription.ID
	}

	if customerID != "" {
		if err := h.users.UpdateStripeCustomer(r.Context(), userID, customerID); err != nil {
			log.Printf("VerifyCheckout: failed to save customer ID: %v", err)
		}
	}
	if subscriptionID != "" && customerID != "" {
		if err := h.users.ActivateSubscription(r.Context(), customerID, subscriptionID, "active"); err != nil {
			log.Printf("VerifyCheckout: failed to activate subscription: %v", err)
		}
	}

	log.Printf("Checkout verified for user %s, customer %s, subscription %s", userID, customerID, subscriptionID)
	writeJSON(w, http.StatusOK, map[string]string{"subscription_status": "active"})
}

// CreatePortalSession creates a Stripe Billing Portal session so the user can
// manage their subscription (cancel, update payment method, view invoices).
func (h *StripeHandler) CreatePortalSession(w http.ResponseWriter, r *http.Request) {
	userID := middleware.GetUserID(r.Context())

	user, err := h.users.GetByID(r.Context(), userID)
	if err != nil || user == nil {
		log.Printf("Portal: failed to get user %s: %v", userID, err)
		writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", "Failed to load user")
		return
	}

	if user.StripeCustomerID == nil || *user.StripeCustomerID == "" {
		writeError(w, http.StatusBadRequest, "NO_CUSTOMER", "No Stripe customer on file")
		return
	}

	params := &stripe.BillingPortalSessionParams{
		Customer:  user.StripeCustomerID,
		ReturnURL: stripe.String(h.cfg.FrontendURL + "/account"),
	}

	s, err := portalsession.New(params)
	if err != nil {
		log.Printf("Portal: failed to create portal session: %v", err)
		writeError(w, http.StatusInternalServerError, "STRIPE_ERROR", "Failed to create portal session")
		return
	}

	writeJSON(w, http.StatusOK, map[string]string{"url": s.URL})
}

// HandleWebhook processes incoming Stripe webhook events.
// This endpoint must NOT require authentication (Stripe calls it directly).
func (h *StripeHandler) HandleWebhook(w http.ResponseWriter, r *http.Request) {
	payload, err := io.ReadAll(io.LimitReader(r.Body, webhookMaxBodyBytes))
	if err != nil {
		log.Printf("Error reading webhook body: %v", err)
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	sig := r.Header.Get("Stripe-Signature")
	event, err := webhook.ConstructEvent(payload, sig, h.cfg.StripeWebhookSecret)
	if err != nil {
		log.Printf("Webhook signature verification failed: %v", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	switch event.Type {
	case "checkout.session.completed":
		h.handleCheckoutCompleted(r, event)
	case "customer.subscription.updated":
		h.handleSubscriptionUpdated(r, event)
	case "customer.subscription.deleted":
		h.handleSubscriptionDeleted(r, event)
	default:
		log.Printf("Unhandled Stripe event type: %s", event.Type)
	}

	w.WriteHeader(http.StatusOK)
}

func (h *StripeHandler) handleCheckoutCompleted(r *http.Request, event stripe.Event) {
	var session stripe.CheckoutSession
	if err := json.Unmarshal(event.Data.Raw, &session); err != nil {
		log.Printf("Error parsing checkout session: %v", err)
		return
	}

	userID := session.ClientReferenceID
	if userID == "" {
		log.Println("Checkout session missing client_reference_id")
		return
	}

	customerID := ""
	if session.Customer != nil {
		customerID = session.Customer.ID
	}
	subscriptionID := ""
	if session.Subscription != nil {
		subscriptionID = session.Subscription.ID
	}

	if customerID != "" {
		if err := h.users.UpdateStripeCustomer(r.Context(), userID, customerID); err != nil {
			log.Printf("Failed to save Stripe customer ID: %v", err)
		}
	}

	if subscriptionID != "" && customerID != "" {
		if err := h.users.ActivateSubscription(r.Context(), customerID, subscriptionID, "active"); err != nil {
			log.Printf("Failed to activate subscription: %v", err)
		}
	}

	log.Printf("Checkout completed for user %s, customer %s, subscription %s", userID, customerID, subscriptionID)
}

func (h *StripeHandler) handleSubscriptionUpdated(r *http.Request, event stripe.Event) {
	var sub stripe.Subscription
	if err := json.Unmarshal(event.Data.Raw, &sub); err != nil {
		log.Printf("Error parsing subscription update: %v", err)
		return
	}

	customerID := ""
	if sub.Customer != nil {
		customerID = sub.Customer.ID
	}
	if customerID == "" {
		return
	}

	status := string(sub.Status)
	if err := h.users.ActivateSubscription(r.Context(), customerID, sub.ID, status); err != nil {
		log.Printf("Failed to update subscription status: %v", err)
	}

	log.Printf("Subscription %s updated to %s for customer %s", sub.ID, status, customerID)
}

func (h *StripeHandler) handleSubscriptionDeleted(r *http.Request, event stripe.Event) {
	var sub stripe.Subscription
	if err := json.Unmarshal(event.Data.Raw, &sub); err != nil {
		log.Printf("Error parsing subscription deletion: %v", err)
		return
	}

	customerID := ""
	if sub.Customer != nil {
		customerID = sub.Customer.ID
	}
	if customerID == "" {
		return
	}

	if err := h.users.UpdateSubscriptionStatus(r.Context(), customerID, "canceled"); err != nil {
		log.Printf("Failed to mark subscription canceled: %v", err)
	}

	log.Printf("Subscription canceled for customer %s", customerID)
}
