package server

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/stripe/stripe-go/v83"
)

type stubStripeEventService struct {
	intentCalled bool
	chargeCalled bool
	err          error
}

func (s *stubStripeEventService) HandlePaymentIntentSucceeded(ctx context.Context, pi *stripe.PaymentIntent) error {
	s.intentCalled = true
	return s.err
}

func (s *stubStripeEventService) HandleChargeRefunded(ctx context.Context, ch *stripe.Charge) error {
	s.chargeCalled = true
	return s.err
}

func TestHandleStripeWebhookPaymentIntent(t *testing.T) {
	secret := "whsec_test"
	payload := map[string]any{
		"id":          "evt_test",
		"type":        "payment_intent.succeeded",
		"api_version": stripe.APIVersion,
		"data": map[string]any{
			"object": map[string]any{
				"id":                   "pi_123",
				"amount":               10000,
				"amount_received":      10000,
				"currency":             "eur",
				"status":               "succeeded",
				"metadata":             map[string]string{"booking_id": "42"},
				"payment_method_types": []string{"card"},
			},
		},
	}

	payloadBytes, err := json.Marshal(payload)
	if err != nil {
		t.Fatalf("failed to marshal payload: %v", err)
	}

	restore := stubConstructEvent(stripe.EventTypePaymentIntentSucceeded, payloadBytes)
	defer restore()

	service := &stubStripeEventService{}
	handler := handleStripeWebhook(service, secret)

	req := httptest.NewRequest(http.MethodPost, "/webhooks/stripe", strings.NewReader(string(payloadBytes)))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Stripe-Signature", "test")
	rec := httptest.NewRecorder()

	handler(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", rec.Code)
	}

	if !service.intentCalled {
		t.Fatalf("expected payment intent handler to be called")
	}
}

func TestHandleStripeWebhookChargeRefunded(t *testing.T) {
	secret := "whsec_test"
	payload := map[string]any{
		"id":          "evt_charge",
		"type":        "charge.refunded",
		"api_version": stripe.APIVersion,
		"data": map[string]any{
			"object": map[string]any{
				"id":              "ch_123",
				"amount":          5000,
				"amount_refunded": 5000,
				"payment_intent": map[string]any{
					"id": "pi_123",
				},
			},
		},
	}

	payloadBytes, _ := json.Marshal(payload)
	restore := stubConstructEvent(stripe.EventTypeChargeRefunded, payloadBytes)
	defer restore()

	service := &stubStripeEventService{}
	handler := handleStripeWebhook(service, secret)

	req := httptest.NewRequest(http.MethodPost, "/webhooks/stripe", strings.NewReader(string(payloadBytes)))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Stripe-Signature", "test")
	rec := httptest.NewRecorder()

	handler(rec, req)

	if !service.chargeCalled {
		t.Fatalf("expected charge handler to be called")
	}
}

func TestHandleStripeWebhookInvalidSignature(t *testing.T) {
	req := httptest.NewRequest(http.MethodPost, "/webhooks/stripe", strings.NewReader("{}"))
	req.Header.Set("Stripe-Signature", "invalid")
	rec := httptest.NewRecorder()

	handler := handleStripeWebhook(&stubStripeEventService{}, "secret")
	handler(rec, req)

	if rec.Code != http.StatusBadRequest {
		t.Fatalf("expected 400 status, got %d", rec.Code)
	}
}

func stubConstructEvent(eventType stripe.EventType, payload []byte) func() {
	original := constructEvent
	constructEvent = func(_ []byte, _ string, _ string) (stripe.Event, error) {
		event := stripe.Event{Type: eventType}
		event.Data = &stripe.EventData{Raw: payload}
		return event, nil
	}
	return func() { constructEvent = original }
}
