package handler

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log/slog"
	"net/http"

	"github.com/flowy-live/llink/internal/billing"
	"github.com/flowy-live/llink/internal/middleware"
	"github.com/flowy-live/llink/internal/network"
)

type CreateCheckoutSessionRequest struct {
	Cadence string `json:"cadence"`
}

type CheckoutSessionResponse struct {
	URL string `json:"url"`
}

type PortalSessionResponse struct {
	URL string `json:"url"`
}

// GetNetworkUsage reports today's usage, daily limit (nil on pro), and reset
// time. Open to any network member since the UI surfaces it to every sender.
func (h *Handler) GetNetworkUsage(w http.ResponseWriter, r *http.Request) {
	humanId, ok := middleware.HumanIdFromContext(r.Context())
	if !ok {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	networkID := r.PathValue("id")
	if networkID == "" {
		http.Error(w, "network id is required", http.StatusBadRequest)
		return
	}

	isMember, err := h.networkSvc.IsMember(r.Context(), networkID, humanId)
	if err != nil {
		slog.Error("failed to check network membership", "error", err, "network_id", networkID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	if !isMember {
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}

	usage, err := h.billingSvc.GetUsage(r.Context(), networkID)
	if err != nil {
		slog.Error("failed to get network usage", "error", err, "network_id", networkID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	writeJSON(w, usage)
}

func (h *Handler) GetNetworkBilling(w http.ResponseWriter, r *http.Request) {
	net, _, ok := h.loadNetworkForAdmin(w, r)
	if !ok {
		return
	}

	status, err := h.billingSvc.GetStatus(r.Context(), net.ID)
	if err != nil {
		slog.Error("failed to get billing status", "error", err, "network_id", net.ID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	writeJSON(w, status)
}

func (h *Handler) CreateCheckoutSession(w http.ResponseWriter, r *http.Request) {
	net, adminHumanId, ok := h.loadNetworkForAdmin(w, r)
	if !ok {
		return
	}

	var req CreateCheckoutSessionRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}

	cadence := billing.Cadence(req.Cadence)
	if !cadence.IsValid() {
		http.Error(w, "invalid cadence", http.StatusBadRequest)
		return
	}

	adminHuman, err := h.humanSvc.GetByID(r.Context(), adminHumanId)
	if err != nil {
		slog.Error("failed to load admin human", "error", err, "human_id", adminHumanId)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	seats, err := h.networkSvc.CountSeats(r.Context(), net.ID)
	if err != nil {
		slog.Error("failed to count seats", "error", err, "network_id", net.ID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	url, err := h.billingSvc.CreateCheckoutSession(r.Context(), billing.CheckoutParams{
		NetworkID:    net.ID,
		AdminHumanID: adminHumanId,
		AdminEmail:   adminHuman.Email,
		Cadence:      cadence,
		Seats:        seats,
	})
	if err != nil {
		slog.Error("failed to create checkout session", "error", err, "network_id", net.ID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	writeJSON(w, CheckoutSessionResponse{URL: url})
}

func (h *Handler) CreatePortalSession(w http.ResponseWriter, r *http.Request) {
	net, _, ok := h.loadNetworkForAdmin(w, r)
	if !ok {
		return
	}

	url, err := h.billingSvc.CreatePortalSession(r.Context(), net.ID)
	if errors.Is(err, billing.ErrNoActiveSubscription) {
		http.Error(w, "no active subscription", http.StatusConflict)
		return
	}
	if err != nil {
		slog.Error("failed to create portal session", "error", err, "network_id", net.ID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	writeJSON(w, PortalSessionResponse{URL: url})
}

const maxStripeWebhookBytes = 1 << 20 // 1 MiB

func (h *Handler) HandleStripeWebhook(w http.ResponseWriter, r *http.Request) {
	payload, err := io.ReadAll(io.LimitReader(r.Body, maxStripeWebhookBytes))
	if err != nil {
		slog.Warn("stripe webhook: failed to read body", "error", err)
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}
	signature := r.Header.Get("Stripe-Signature")

	if err := h.billingSvc.HandleWebhook(r.Context(), payload, signature); err != nil {
		slog.Error("stripe webhook failed", "error", err)
		formattedErr := fmt.Errorf("webhook processing failed: %w", err)
		http.Error(w, formattedErr.Error(), http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusOK)
}

// Resolves {id}, verifies the caller is admin. On failure writes the HTTP
// error and returns ok=false.
func (h *Handler) loadNetworkForAdmin(w http.ResponseWriter, r *http.Request) (*network.Network, string, bool) {
	humanId, ok := middleware.HumanIdFromContext(r.Context())
	if !ok {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return nil, "", false
	}

	networkID := r.PathValue("id")
	if networkID == "" {
		http.Error(w, "network id is required", http.StatusBadRequest)
		return nil, "", false
	}

	net, err := h.networkSvc.GetByID(r.Context(), networkID)
	if errors.Is(err, network.ErrNotFound) {
		http.Error(w, "network not found", http.StatusNotFound)
		return nil, "", false
	}
	if err != nil {
		slog.Error("failed to load network", "error", err, "network_id", networkID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return nil, "", false
	}

	if net.AdminHumanId != humanId {
		http.Error(w, "forbidden", http.StatusForbidden)
		return nil, "", false
	}

	return net, humanId, true
}

func writeJSON(w http.ResponseWriter, body any) {
	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(body); err != nil {
		slog.Error("failed to write json", "error", err)
	}
}
