//go:build test

package admin

// Test coverage for UpdateBookingServicesHandler: PUT /api/admin/bookings/{id}
//
// Tests cover:
//   - Successful service replacement, addition, removal
//   - Price and duration overrides
//   - Notes update
//   - Validation: invalid IDs, empty services, missing booking, invalid overrides
//   - Status rejection: completed, cancelled, no_show
//   - Overlap detection with next booking
//   - Response shape verification

import (
	"context"
	"net/http"
	"testing"
	"time"

	"crussell/clock"
	"crussell/db"
	"crussell/handlers/bookings"
	"crussell/testutils"
	"crussell/testutils/fixtures"
)

// =============================================================================
// Helpers
// =============================================================================

// createBookingWithStartTime creates a booking at a specific start time with the given service
func createBookingWithStartTime(t *testing.T, tx db.Querier, ctx context.Context, userID, serviceID string, startTime time.Time, status string) string {
	t.Helper()

	var bookingID string
	err := tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status, notes)
		VALUES ($1, $2, $3, $4)
		RETURNING id
	`, userID, startTime, status, "").Scan(&bookingID)
	if err != nil {
		t.Fatalf("failed to create booking: %v", err)
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO booking_services (booking_id, service_id)
		VALUES ($1, $2)
	`, bookingID, serviceID)
	if err != nil {
		t.Fatalf("failed to link service to booking: %v", err)
	}
	return bookingID
}

// createSecondService creates an additional test service with the given duration
func createSecondService(t *testing.T, tx db.Querier, ctx context.Context, name string, durationMinutes int, price float64) string {
	t.Helper()

	var serviceID string
	err := tx.QueryRow(ctx, `
		INSERT INTO services (name, description, price, duration_minutes, is_active)
		VALUES ($1, $2, $3, $4, true)
		RETURNING id
	`, name, "Test service: "+name, price, durationMinutes).Scan(&serviceID)
	if err != nil {
		t.Fatalf("failed to create service %s: %v", name, err)
	}
	return serviceID
}

// =============================================================================
// Success Cases
// =============================================================================

// TestAdminBookings_UpdateServices_ReplaceServices verifies that an admin can
// replace all services on a booking with a new set of services.
func TestAdminBookings_UpdateServices_ReplaceServices(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service 1: %v", err)
	}

	service2 := createSecondService(t, tx, ctx, "Service Two", 45, 55.00)

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service2},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var resp bookings.Booking
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	if len(resp.Services) != 1 {
		t.Errorf("expected 1 service, got %d", len(resp.Services))
	}
	if resp.Services[0].ServiceID != service2 {
		t.Errorf("expected service %s, got %s", service2, resp.Services[0].ServiceID)
	}
}

// TestAdminBookings_UpdateServices_AddService verifies that an admin can add
// additional services to an existing booking.
func TestAdminBookings_UpdateServices_AddService(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service 1: %v", err)
	}

	service2 := createSecondService(t, tx, ctx, "Service Two", 30, 40.00)

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1, service2},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var resp bookings.Booking
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	if len(resp.Services) != 2 {
		t.Errorf("expected 2 services, got %d", len(resp.Services))
	}
}

// TestAdminBookings_UpdateServices_RemoveService verifies that an admin can
// remove services from a booking by providing fewer service IDs.
func TestAdminBookings_UpdateServices_RemoveService(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service 1: %v", err)
	}

	service2 := createSecondService(t, tx, ctx, "Service Two", 30, 40.00)

	// Create booking with service1
	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	// Manually add service2 to the booking

	_, err = tx.Exec(ctx, `
		INSERT INTO booking_services (booking_id, service_id)
		VALUES ($1, $2)
	`, bookingID, service2)
	if err != nil {
		t.Fatalf("failed to add second service: %v", err)
	}

	// Update to only service1 (removing service2)
	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var resp bookings.Booking
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	if len(resp.Services) != 1 {
		t.Errorf("expected 1 service after removal, got %d", len(resp.Services))
	}
}

// TestAdminBookings_UpdateServices_WithPriceOverride verifies that an admin can
// apply a price override to a service.
func TestAdminBookings_UpdateServices_WithPriceOverride(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
		"service_overrides": []map[string]interface{}{
			{
				"service_id":     service1,
				"override_price": 25.00,
			},
		},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var resp bookings.Booking
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	if resp.Services[0].OverridePrice == nil {
		t.Fatal("expected override_price to be set")
	}
	if *resp.Services[0].OverridePrice != 25.00 {
		t.Errorf("expected override_price 25.00, got %f", *resp.Services[0].OverridePrice)
	}
}

// TestAdminBookings_UpdateServices_WithDurationOverride verifies that an admin can
// apply a duration override to a service.
func TestAdminBookings_UpdateServices_WithDurationOverride(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
		"service_overrides": []map[string]interface{}{
			{
				"service_id":                service1,
				"override_duration_minutes": 90,
			},
		},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var resp bookings.Booking
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	if resp.Services[0].OverrideDurationMinutes == nil {
		t.Fatal("expected override_duration_minutes to be set")
	}
	if *resp.Services[0].OverrideDurationMinutes != 90 {
		t.Errorf("expected override_duration_minutes 90, got %d", *resp.Services[0].OverrideDurationMinutes)
	}
}

// TestAdminBookings_UpdateServices_WithBothOverrides verifies that an admin can
// apply both price and duration overrides simultaneously.
func TestAdminBookings_UpdateServices_WithBothOverrides(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
		"service_overrides": []map[string]interface{}{
			{
				"service_id":                service1,
				"override_price":            30.00,
				"override_duration_minutes": 75,
			},
		},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var resp bookings.Booking
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	if resp.Services[0].OverridePrice == nil || *resp.Services[0].OverridePrice != 30.00 {
		t.Errorf("expected override_price 30.00, got %v", resp.Services[0].OverridePrice)
	}
	if resp.Services[0].OverrideDurationMinutes == nil || *resp.Services[0].OverrideDurationMinutes != 75 {
		t.Errorf("expected override_duration_minutes 75, got %v", resp.Services[0].OverrideDurationMinutes)
	}
}

// TestAdminBookings_UpdateServices_UpdateNotes verifies that an admin can update
// the booking notes along with services.
func TestAdminBookings_UpdateServices_UpdateNotes(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
		"notes":       "Updated notes for this booking",
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var resp bookings.Booking
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	if resp.Notes == nil || *resp.Notes != "Updated notes for this booking" {
		t.Errorf("expected notes 'Updated notes for this booking', got %v", resp.Notes)
	}
}

// TestAdminBookings_UpdateServices_MultipleOverrides verifies that an admin can
// apply overrides to multiple services in a single request.
func TestAdminBookings_UpdateServices_MultipleOverrides(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service 1: %v", err)
	}

	service2 := createSecondService(t, tx, ctx, "Service Two", 45, 55.00)

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1, service2},
		"service_overrides": []map[string]interface{}{
			{
				"service_id":     service1,
				"override_price": 20.00,
			},
			{
				"service_id":                service2,
				"override_duration_minutes": 60,
			},
		},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var resp bookings.Booking
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	if len(resp.Services) != 2 {
		t.Fatalf("expected 2 services, got %d", len(resp.Services))
	}

	// Find each service and verify its override
	for _, svc := range resp.Services {
		if svc.ServiceID == service1 {
			if svc.OverridePrice == nil || *svc.OverridePrice != 20.00 {
				t.Errorf("service1: expected override_price 20.00, got %v", svc.OverridePrice)
			}
		}
		if svc.ServiceID == service2 {
			if svc.OverrideDurationMinutes == nil || *svc.OverrideDurationMinutes != 60 {
				t.Errorf("service2: expected override_duration_minutes 60, got %v", svc.OverrideDurationMinutes)
			}
		}
	}
}

// =============================================================================
// Validation Error Cases
// =============================================================================

// TestAdminBookings_UpdateServices_InvalidBookingID verifies that an invalid
// booking ID returns 404.
func TestAdminBookings_UpdateServices_InvalidBookingID(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)
	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/invalid-id", reqBody, ctx)

	if w.Code != http.StatusNotFound {
		t.Errorf("expected status 404, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_BookingNotFound verifies that a valid-format
// but non-existent booking ID returns 404.
func TestAdminBookings_UpdateServices_BookingNotFound(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)
	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/abc123def456", reqBody, ctx)

	if w.Code != http.StatusNotFound {
		t.Errorf("expected status 404, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_EmptyServiceIDs verifies that an empty
// service_ids array returns 400.
func TestAdminBookings_UpdateServices_EmptyServiceIDs(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)
	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_InvalidServiceID verifies that an invalid
// service ID in the list returns 400.
func TestAdminBookings_UpdateServices_InvalidServiceID(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)
	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{"invalid-service-id"},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_ServiceNotFound verifies that a valid-format
// but non-existent service ID returns 400.
func TestAdminBookings_UpdateServices_ServiceNotFound(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)
	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{"abc123def456"},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_NegativePriceOverride verifies that a negative
// price override returns 400.
func TestAdminBookings_UpdateServices_NegativePriceOverride(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)
	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
		"service_overrides": []map[string]interface{}{
			{
				"service_id":     service1,
				"override_price": -10.00,
			},
		},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_ZeroDurationOverride verifies that a zero or
// negative duration override returns 400.
func TestAdminBookings_UpdateServices_ZeroDurationOverride(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)
	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
		"service_overrides": []map[string]interface{}{
			{
				"service_id":                service1,
				"override_duration_minutes": 0,
			},
		},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
	}
}

// =============================================================================
// Status Rejection Cases
// =============================================================================

// TestAdminBookings_UpdateServices_CompletedBookingRejected verifies that
// updating services on a completed booking returns 403.
func TestAdminBookings_UpdateServices_CompletedBookingRejected(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)
	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(-1*time.Hour), "completed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusForbidden {
		t.Errorf("expected status 403, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_CancelledBookingRejected verifies that
// updating services on a cancelled booking returns 403.
func TestAdminBookings_UpdateServices_CancelledBookingRejected(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)
	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "client_cancelled")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusForbidden {
		t.Errorf("expected status 403, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_NoShowBookingRejected verifies that
// updating services on a no-show booking returns 403.
func TestAdminBookings_UpdateServices_NoShowBookingRejected(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)
	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "no_show")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusForbidden {
		t.Errorf("expected status 403, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_WeCancelledBookingRejected verifies that
// updating services on a we_cancelled booking returns 403.
func TestAdminBookings_UpdateServices_WeCancelledBookingRejected(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)
	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "we_cancelled")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusForbidden {
		t.Errorf("expected status 403, got %d. body: %s", w.Code, w.Body.String())
	}
}

// =============================================================================
// Overlap Detection Cases
// =============================================================================

// TestAdminBookings_UpdateServices_OverlapWithNextBooking verifies that extending
// a booking's duration to overlap with the next booking returns 409.
func TestAdminBookings_UpdateServices_OverlapWithNextBooking(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	// Create a long-duration service for the overlap test
	longService := createSecondService(t, tx, ctx, "Long Service", 300, 100.00) // 5 hours

	now := clock.Now()
	// Booking 1 at 10:00 tomorrow
	booking1Start := now.Add(24 * time.Hour).Truncate(24 * time.Hour).Add(10 * time.Hour)
	bookingID1 := createBookingWithStartTime(t, tx, ctx, userID, service1, booking1Start, "confirmed")

	// Booking 2 at 11:00 tomorrow (1 hour after booking 1)
	booking2Start := booking1Start.Add(1 * time.Hour)
	_ = createBookingWithStartTime(t, tx, ctx, userID, service1, booking2Start, "confirmed")

	// Try to update booking 1 to use the 5-hour service (would overlap booking 2)
	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{longService},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID1, reqBody, ctx)

	if w.Code != http.StatusConflict {
		t.Errorf("expected status 409, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_NoOverlapSucceeds verifies that a service update
// that does not overlap with the next booking succeeds.
func TestAdminBookings_UpdateServices_NoOverlapSucceeds(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	service2 := createSecondService(t, tx, ctx, "Service Two", 30, 40.00)

	now := clock.Now()
	// Booking 1 at 10:00 tomorrow
	booking1Start := now.Add(24 * time.Hour).Truncate(24 * time.Hour).Add(10 * time.Hour)
	bookingID1 := createBookingWithStartTime(t, tx, ctx, userID, service1, booking1Start, "confirmed")

	// Booking 2 at 14:00 tomorrow (4 hours after booking 1 starts)
	booking2Start := booking1Start.Add(4 * time.Hour)
	_ = createBookingWithStartTime(t, tx, ctx, userID, service1, booking2Start, "confirmed")

	// Update booking 1 to have both services (total ~60 min, well within the 4-hour gap)
	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1, service2},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID1, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_NoNextBookingSucceeds verifies that a service
// update succeeds when there is no next booking (no overlap possible).
func TestAdminBookings_UpdateServices_NoNextBookingSucceeds(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	longService := createSecondService(t, tx, ctx, "Long Service", 300, 100.00)

	// Only booking for the day — no next booking to conflict with
	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{longService},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}
}

// =============================================================================
// Response Shape Verification
// =============================================================================

// TestAdminBookings_UpdateServices_ResponseShape verifies that the response
// contains all expected fields after a successful update.
func TestAdminBookings_UpdateServices_ResponseShape(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
		"notes":       "Test notes for response shape verification",
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var resp bookings.Booking
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	// Verify all top-level fields are present
	if resp.ID == "" {
		t.Error("expected non-empty ID")
	}
	if resp.StartTime.IsZero() {
		t.Error("expected non-zero StartTime")
	}
	if resp.Status == "" {
		t.Error("expected non-empty Status")
	}
	if resp.Notes == nil {
		t.Error("expected non-nil Notes")
	}
	if resp.User == nil {
		t.Error("expected non-nil User")
	}
	if resp.User.FullName == "" {
		t.Error("expected non-empty User.FullName")
	}
	if len(resp.Services) == 0 {
		t.Error("expected at least one service in response")
	}
}

// =============================================================================
// Edge Cases
// =============================================================================

// TestAdminBookings_UpdateServices_PendingBooking verifies that services can be
// updated on a pending booking.
func TestAdminBookings_UpdateServices_PendingBooking(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "pending")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_InProgressBooking verifies that services can be
// updated on an in_progress booking.
func TestAdminBookings_UpdateServices_InProgressBooking(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(-30*time.Minute), "in_progress")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_UpdateServices_ClearNotes verifies that setting notes to an
// empty string updates the booking notes accordingly.
func TestAdminBookings_UpdateServices_ClearNotes(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create test user: %v", err)
	}

	service1, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	bookingID := createBookingWithStartTime(t, tx, ctx, userID, service1, clock.Now().Add(24*time.Hour), "confirmed")

	handler := http.HandlerFunc(bookings.UpdateBookingServicesHandler)
	reqBody := map[string]interface{}{
		"service_ids": []string{service1},
		"notes":       "",
	}
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID, reqBody, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}
}
