//go:build test

package admin

// Package admin contains tests for admin booking management endpoints.
//
// Test Coverage:
//   - GetAllAdminBookingsHandler: GET /api/admin/bookings - List all bookings with filters
//   - GetAllAdminBookingsHandler Pagination: per_page cap up to 500, fallback to 10
//   - SearchAdminBookingsHandler: POST /api/admin/bookings/search - Search bookings
//   - GetAdminBookingHandler: GET /api/admin/bookings/{id} - Get booking details
//   - AdminCreateBookingForUserHandler: POST /api/admin/bookings - Create booking for user
//   - ProgressBookingHandler: PUT /api/admin/bookings/{id}/progress - Update booking status
//   - ConfirmBookingHandler: POST /api/admin/bookings/{id}/confirm - Confirm booking
//   - AdminCancelBookingHandler: POST /api/admin/bookings/{id}/cancel - Cancel booking
//   - AdminListEditRequestsHandler: GET /api/admin/bookings/edit-requests - List edit requests
//   - AdminApproveEditRequestHandler: POST /api/admin/bookings/{id}/approve-edit - Approve edit
//   - AdminRejectEditRequestHandler: POST /api/admin/bookings/{id}/reject-edit - Reject edit
//
// Authentication: All endpoints require admin role (403 for non-admins).

import (
	"bytes"
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

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

	"github.com/go-chi/chi/v5"
)

// =============================================================================
// List Admin Bookings Tests
// =============================================================================

// TestAdminBookings_List verifies that an admin can list all bookings in the
// system with pagination support.
func TestAdminBookings_List(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)
	}

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

	// Insert name history for the user
	_, err = tx.Exec(ctx, `
		INSERT INTO name_history (user_id, previous_first_name, previous_last_name)
		VALUES ($1, 'OldFirst', 'OldLast')
	`, userID)
	if err != nil {
		t.Fatalf("failed to insert name_history: %v", err)
	}

	_, err = fixtures.CreateTestBooking(tx, userID, serviceID)
	if err != nil {
		t.Fatalf("failed to create test booking 1: %v", err)
	}

	_, err = fixtures.CreateTestBooking(tx, userID, serviceID)
	if err != nil {
		t.Fatalf("failed to create test booking 2: %v", err)
	}

	handler := http.HandlerFunc(bookings.GetAllAdminBookingsHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings", nil, ctx)

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

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

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

	if resp.Total != 2 {
		t.Errorf("expected total 2, got %d", resp.Total)
	}

	// Verify name history appears in booking user info
	for _, b := range resp.Bookings {
		if b.User == nil {
			continue
		}
		if b.User.PreviousFirstName == nil || *b.User.PreviousFirstName != "OldFirst" {
			t.Errorf("expected previousFirstName 'OldFirst' in booking, got %v", b.User.PreviousFirstName)
		}
		if b.User.PreviousLastName == nil || *b.User.PreviousLastName != "OldLast" {
			t.Errorf("expected previousLastName 'OldLast' in booking, got %v", b.User.PreviousLastName)
		}
	}
}

// TestAdminBookings_List_PerPageCap tests that per_page parameter up to 500
// is accepted (was previously capped at 100, causing per_page=500 to fall
// back to default 10 and miss bookings on page 2+).
func TestAdminBookings_List_PerPageCap(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)
	}

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

	// Create 3 bookings so we can verify per_page=500 returns all of them
	for i := 0; i < 3; i++ {
		_, err = fixtures.CreateTestBooking(tx, userID, serviceID)
		if err != nil {
			t.Fatalf("failed to create test booking %d: %v", i+1, err)
		}
	}

	// Test per_page=500 (should be accepted and return all 3 bookings)
	handler := http.HandlerFunc(bookings.GetAllAdminBookingsHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings?per_page=500", nil, ctx)

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

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

	if len(resp.Bookings) != 3 {
		t.Errorf("expected 3 bookings with per_page=500, got %d (per_page was likely rejected)", len(resp.Bookings))
	}
	if resp.PerPage != 500 {
		t.Errorf("expected per_page=500 in response, got %d", resp.PerPage)
	}
	if resp.Total != 3 {
		t.Errorf("expected total 3, got %d", resp.Total)
	}

	// Test per_page=600 (should be rejected and fall back to default 10)
	w2 := makeAdminRequest(handler, "GET", "/api/admin/bookings?per_page=600", nil, ctx)
	if w2.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", w2.Code)
	}
	var resp2 bookings.BookingListResponse
	if err := parseResponseBody(w2, &resp2); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}
	if resp2.PerPage != 10 {
		t.Errorf("expected per_page defaults to 10 when >500, got %d", resp2.PerPage)
	}
}

// TestAdminBookings_List_FilterByStatus tests that an admin can filter
// bookings by status (e.g., pending, confirmed, completed).
func TestAdminBookings_List_FilterByStatus(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)
	}

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

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

	handler := http.HandlerFunc(bookings.GetAllAdminBookingsHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings?status=pending", nil, ctx)

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

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

	if len(resp.Bookings) != 1 {
		t.Errorf("expected 1 pending booking, got %d", len(resp.Bookings))
	}

	w = makeAdminRequest(handler, "GET", "/api/admin/bookings?status=completed", nil, ctx)

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

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

	if len(resp.Bookings) != 0 {
		t.Errorf("expected 0 bookings for completed status, got %d", len(resp.Bookings))
	}
}

// =============================================================================
// Admin Create Booking Tests
// =============================================================================

// TestAdminBookings_Create verifies that an admin can create a booking
// on behalf of a user. The booking is created with 'confirmed' status.
func TestAdminBookings_Create(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)
	}

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

	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	req := bookings.AdminCreateBookingForUserRequest{
		UserID:     userID,
		StartTime:  futureTime,
		ServiceIDs: []string{serviceID},
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

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

	// Parse response with new format {"booking": {...}, "warnings": [...]}
	var response map[string]interface{}
	if err := parseResponseBody(w, &response); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	bookingData, ok := response["booking"].(map[string]interface{})
	if !ok {
		t.Fatal("expected booking in response")
	}

	status, ok := bookingData["status"].(string)
	if !ok {
		t.Fatal("expected status in booking")
	}
	if status != "confirmed" {
		t.Errorf("expected status 'confirmed', got %s", status)
	}

	var count int
	err = tx.QueryRow(ctx,
		"SELECT COUNT(*) FROM bookings WHERE user_id = $1", userID).Scan(&count)
	if err != nil {
		t.Errorf("failed to query bookings: %v", err)
	}
	if count != 1 {
		t.Errorf("expected 1 booking, got %d", count)
	}
}

// TestAdminBookings_Create_InvalidInput verifies that admin booking
// creation fails with HTTP 400 when required fields (userID, startTime, serviceIDs)
// are missing or invalid.
func TestAdminBookings_Create_InvalidInput(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)
	}

	tests := []struct {
		name string
		req  bookings.AdminCreateBookingForUserRequest
	}{
		{
			name: "missing user ID",
			req: bookings.AdminCreateBookingForUserRequest{
				StartTime:  clock.Now().Add(72 * time.Hour),
				ServiceIDs: []string{"some-service-id"},
			},
		},
		{
			name: "missing start time",
			req: bookings.AdminCreateBookingForUserRequest{
				UserID:     userID,
				ServiceIDs: []string{"some-service-id"},
			},
		},
		{
			name: "missing service IDs",
			req: bookings.AdminCreateBookingForUserRequest{
				UserID:    userID,
				StartTime: clock.Now().Add(72 * time.Hour),
			},
		},
		{
			name: "empty service IDs",
			req: bookings.AdminCreateBookingForUserRequest{
				UserID:     userID,
				StartTime:  clock.Now().Add(72 * time.Hour),
				ServiceIDs: []string{},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
			w := makeAdminRequest(handler, "POST", "/api/admin/bookings", tt.req, ctx)

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

// =============================================================================
// Search Admin Bookings Tests
// =============================================================================

// TestAdminBookings_Search tests that an admin can search bookings by
// notes, customer name, or other text fields.
func TestAdminBookings_Search(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)
	}

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

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

	_, err = tx.Exec(ctx,
		"UPDATE bookings SET notes = 'Test booking for search' WHERE id = $1", bookingID)
	if err != nil {
		t.Fatalf("failed to update booking notes: %v", err)
	}

	handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=Test", nil, ctx)

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

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

	if len(resp.Bookings) != 1 {
		t.Errorf("expected 1 booking, got %d", len(resp.Bookings))
	}
}

// TestAdminBookings_Search_MissingQuery verifies that searching without
// a query parameter returns HTTP 400 Bad Request.
func TestAdminBookings_Search_MissingQuery(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)
	}

	handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search", nil, ctx)

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

// =============================================================================
// Get Single Admin Booking Tests
// =============================================================================

// TestAdminBookings_Get verifies that an admin can retrieve a single booking by ID and that
// the response includes populated user and services relationships.
func TestAdminBookings_Get(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)
	}

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

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

	handler := http.HandlerFunc(bookings.GetAdminBookingHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/"+bookingID, nil, ctx)

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

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

	if booking.ID != bookingID {
		t.Errorf("expected booking ID %s, got %s", bookingID, booking.ID)
	}

	if booking.User == nil {
		t.Error("expected user to be populated")
	}

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

// TestAdminBookings_Get_NotFound verifies that requesting a non-existent booking returns 404.
func TestAdminBookings_Get_NotFound(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)
	}

	handler := http.HandlerFunc(bookings.GetAdminBookingHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/nonexistent-id", nil, ctx)

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

// =============================================================================
// Get User's Bookings Tests (Admin view)
// =============================================================================

// TestAdminBookings_GetUserBookings verifies that an admin can retrieve all bookings for a specific user.
func TestAdminBookings_GetUserBookings(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)
	}

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

	_, err = fixtures.CreateTestBooking(tx, userID, serviceID)
	if err != nil {
		t.Fatalf("failed to create test booking 1: %v", err)
	}

	_, err = fixtures.CreateTestBooking(tx, userID, serviceID)
	if err != nil {
		t.Fatalf("failed to create test booking 2: %v", err)
	}

	handler := http.HandlerFunc(bookings.GetAllBookingsByUserHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/user/"+userID, nil, ctx)

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

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

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

	if resp.Total != 2 {
		t.Errorf("expected total 2, got %d", resp.Total)
	}
}

// =============================================================================
// Progress Booking Tests
// =============================================================================

// TestAdminBookings_Progress verifies that an admin can change a booking's status (e.g., pending to confirmed),
// and that the status is correctly updated in both the response and database.
func TestAdminBookings_Progress(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)
	}

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

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

	req := bookings.ProgressBookingRequest{
		Status: "confirmed",
	}

	handler := http.HandlerFunc(bookings.ProgressBookingHandler)
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID+"/progress", req, ctx)

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

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

	if booking.Status != "confirmed" {
		t.Errorf("expected status 'confirmed', got %s", booking.Status)
	}

	var dbStatus string
	err = tx.QueryRow(ctx,
		"SELECT status FROM bookings WHERE id = $1", bookingID).Scan(&dbStatus)
	if err != nil {
		t.Fatalf("failed to query booking: %v", err)
	}
	if dbStatus != "confirmed" {
		t.Errorf("expected status 'confirmed' in DB, got %s", dbStatus)
	}
}

// TestAdminBookings_Progress_InvalidStatus verifies that providing an invalid status value returns 400 Bad Request.
func TestAdminBookings_Progress_InvalidStatus(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)
	}

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

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

	req := bookings.ProgressBookingRequest{
		Status: "invalid_status",
	}

	handler := http.HandlerFunc(bookings.ProgressBookingHandler)
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID+"/progress", req, ctx)

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

// TestAdminBookings_Progress_NotFound verifies that attempting to progress a non-existent booking returns 404.
func TestAdminBookings_Progress_NotFound(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)
	}

	req := bookings.ProgressBookingRequest{
		Status: "confirmed",
	}

	handler := http.HandlerFunc(bookings.ProgressBookingHandler)
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/nonexistent-id/progress", req, ctx)

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

// =============================================================================
// Confirm Booking Tests
// =============================================================================

// TestAdminBookings_Confirm verifies that an admin can confirm a pending booking, updating its status to 'confirmed'.
func TestAdminBookings_Confirm(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)
	}

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

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

	req := bookings.ConfirmBookingRequest{}

	handler := http.HandlerFunc(bookings.ConfirmBookingHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/confirm", req, ctx)

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

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

	if booking.Status != "confirmed" {
		t.Errorf("expected status 'confirmed', got %s", booking.Status)
	}
}

// TestAdminBookings_Confirm_AlreadyConfirmed verifies idempotency - attempting to confirm
// an already-confirmed booking returns 404 Not Found.
func TestAdminBookings_Confirm_AlreadyConfirmed(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)
	}

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

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

	_, err = tx.Exec(ctx, "UPDATE bookings SET status = 'confirmed' WHERE id = $1", bookingID)
	if err != nil {
		t.Fatalf("failed to update booking status: %v", err)
	}

	req := bookings.ConfirmBookingRequest{}

	handler := http.HandlerFunc(bookings.ConfirmBookingHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/confirm", req, ctx)

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

// =============================================================================
// Cancel Booking Tests
// =============================================================================

// TestAdminBookings_Cancel verifies that an admin can cancel a booking, updating its status to 'we_cancelled'.
func TestAdminBookings_Cancel(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)
	}

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

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

	_, err = tx.Exec(ctx, "UPDATE bookings SET status = 'confirmed' WHERE id = $1", bookingID)
	if err != nil {
		t.Fatalf("failed to update booking status: %v", err)
	}

	handler := http.HandlerFunc(bookings.AdminCancelBookingHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/cancel", nil, ctx)

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

	var dbStatus string
	err = tx.QueryRow(ctx,
		"SELECT status FROM bookings WHERE id = $1", bookingID).Scan(&dbStatus)
	if err != nil {
		t.Fatalf("failed to query booking: %v", err)
	}
	if dbStatus != "we_cancelled" {
		t.Errorf("expected status 'we_cancelled' in DB, got %s", dbStatus)
	}
}

// TestAdminBookings_Cancel_NotFound verifies that attempting to cancel a non-existent booking returns 404.
func TestAdminBookings_Cancel_NotFound(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)
	}

	handler := http.HandlerFunc(bookings.AdminCancelBookingHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings/nonexistent-id/cancel", nil, ctx)

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

// TestAdminBookings_Cancel_PendingStatus verifies that admin cancellations of pending bookings
// do NOT create admin notifications (pending cancellations don't require staff attention).
func TestAdminBookings_Cancel_PendingStatus(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)
	}

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

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

	// Booking stays in 'pending' status (no confirmation)

	handler := http.HandlerFunc(bookings.AdminCancelBookingHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/cancel", nil, ctx)

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

	// Verify status changed to we_cancelled
	var dbStatus string
	err = tx.QueryRow(ctx,
		"SELECT status FROM bookings WHERE id = $1", bookingID).Scan(&dbStatus)
	if err != nil {
		t.Fatalf("failed to query booking: %v", err)
	}
	if dbStatus != "we_cancelled" {
		t.Errorf("expected status 'we_cancelled' in DB, got %s", dbStatus)
	}

	// Verify NO admin notification was created for pending cancellations
	var notifCount int
	err = tx.QueryRow(ctx,
		`SELECT COUNT(*) FROM admin_notifications WHERE booking_id = $1`,
		bookingID).Scan(&notifCount)
	if err != nil {
		t.Fatalf("failed to query notifications: %v", err)
	}
	if notifCount != 0 {
		t.Errorf("expected 0 admin notifications for pending cancel, got %d", notifCount)
	}
}

// TestAdminBookings_Cancel_ConfirmedCreatesNotification verifies that cancelling a confirmed
// booking creates an admin notification for staff awareness.
func TestAdminBookings_Cancel_ConfirmedCreatesNotification(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)
	}

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

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

	// Confirm the booking
	_, err = tx.Exec(ctx, "UPDATE bookings SET status = 'confirmed' WHERE id = $1", bookingID)
	if err != nil {
		t.Fatalf("failed to confirm booking: %v", err)
	}

	handler := http.HandlerFunc(bookings.AdminCancelBookingHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/cancel", nil, ctx)

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

	// Verify status changed to we_cancelled
	var dbStatus string
	err = tx.QueryRow(ctx,
		"SELECT status FROM bookings WHERE id = $1", bookingID).Scan(&dbStatus)
	if err != nil {
		t.Fatalf("failed to query booking: %v", err)
	}
	if dbStatus != "we_cancelled" {
		t.Errorf("expected status 'we_cancelled' in DB, got %s", dbStatus)
	}

	// Verify admin notification WAS created for confirmed->cancelled
	var notifCount int
	err = tx.QueryRow(ctx,
		`SELECT COUNT(*) FROM admin_notifications WHERE booking_id = $1 AND reason = 'cancelled_booking'`,
		bookingID).Scan(&notifCount)
	if err != nil {
		t.Fatalf("failed to query notifications: %v", err)
	}
	if notifCount != 1 {
		t.Errorf("expected 1 admin notification for confirmed cancel, got %d", notifCount)
	}
}

// TestAdminBookings_Cancel_InProgressStatus verifies cancellation of in-progress bookings.
func TestAdminBookings_Cancel_InProgressStatus(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)
	}

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

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

	// Set booking to in-progress status
	_, err = tx.Exec(ctx, "UPDATE bookings SET status = 'in_progress' WHERE id = $1", bookingID)
	if err != nil {
		t.Fatalf("failed to set in_progress status: %v", err)
	}

	handler := http.HandlerFunc(bookings.AdminCancelBookingHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/cancel", nil, ctx)

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

	// Verify status changed to we_cancelled
	var dbStatus string
	err = tx.QueryRow(ctx,
		"SELECT status FROM bookings WHERE id = $1", bookingID).Scan(&dbStatus)
	if err != nil {
		t.Fatalf("failed to query booking: %v", err)
	}
	if dbStatus != "we_cancelled" {
		t.Errorf("expected status 'we_cancelled' in DB, got %s", dbStatus)
	}
}

// TestAdminBookings_Cancel_AlreadyCancelledRejectsCancellation verifies that attempting to
// cancel an already-cancelled booking returns 404 Not Found (idempotency guard).
func TestAdminBookings_Cancel_AlreadyCancelledRejectsCancellation(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)
	}

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

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

	// Set to already cancelled
	_, err = tx.Exec(ctx, "UPDATE bookings SET status = 'we_cancelled' WHERE id = $1", bookingID)
	if err != nil {
		t.Fatalf("failed to set we_cancelled status: %v", err)
	}

	// Try to cancel again
	handler := http.HandlerFunc(bookings.AdminCancelBookingHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/cancel", nil, ctx)

	if w.Code != http.StatusNotFound {
		t.Errorf("expected status 404 for already-cancelled booking, got %d", w.Code)
	}
}

// TestAdminBookings_Cancel_CompletedRejectsCancellation verifies that attempting to cancel
// a completed booking returns 404 Not Found (cannot cancel finished appointments).
func TestAdminBookings_Cancel_CompletedRejectsCancellation(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)
	}

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

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

	// Set to completed
	_, err = tx.Exec(ctx, "UPDATE bookings SET status = 'completed' WHERE id = $1", bookingID)
	if err != nil {
		t.Fatalf("failed to set completed status: %v", err)
	}

	// Try to cancel
	handler := http.HandlerFunc(bookings.AdminCancelBookingHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/cancel", nil, ctx)

	if w.Code != http.StatusNotFound {
		t.Errorf("expected status 404 for completed booking, got %d", w.Code)
	}
}

// =============================================================================
// Non-Admin Tests
// =============================================================================

// TestAdminBookings_NonAdmin verifies that regular users receive 403 Forbidden when attempting
// to access any admin booking endpoints (list, get, create, progress, confirm, cancel, search).
func TestAdminBookings_NonAdmin(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

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

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

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

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

	w := makeUserRequest(mw.RequireAdmin(http.HandlerFunc(bookings.GetAllAdminBookingsHandler)), "GET", "/api/admin/bookings", nil, ctx)
	if w.Code != http.StatusForbidden {
		t.Errorf("LIST: expected status 403, got %d", w.Code)
	}

	req := bookings.AdminCreateBookingForUserRequest{
		UserID:     userID,
		StartTime:  clock.Now().Add(72 * time.Hour),
		ServiceIDs: []string{serviceID},
	}
	w = makeUserRequest(mw.RequireAdmin(http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)), "POST", "/api/admin/bookings", req, ctx)
	if w.Code != http.StatusForbidden {
		t.Errorf("CREATE: expected status 403, got %d", w.Code)
	}

	w = makeUserRequest(mw.RequireAdmin(http.HandlerFunc(bookings.SearchAdminBookingsHandler)), "GET", "/api/admin/bookings/search?q=test", nil, ctx)
	if w.Code != http.StatusForbidden {
		t.Errorf("SEARCH: expected status 403, got %d", w.Code)
	}

	w = makeUserRequest(mw.RequireAdmin(http.HandlerFunc(bookings.GetAdminBookingHandler)), "GET", "/api/admin/bookings/"+bookingID, nil, ctx)
	if w.Code != http.StatusForbidden {
		t.Errorf("GET: expected status 403, got %d", w.Code)
	}

	progressReq := bookings.ProgressBookingRequest{Status: "confirmed"}
	w = makeUserRequest(mw.RequireAdmin(http.HandlerFunc(bookings.ProgressBookingHandler)), "PUT", "/api/admin/bookings/"+bookingID+"/progress", progressReq, ctx)
	if w.Code != http.StatusForbidden {
		t.Errorf("PROGRESS: expected status 403, got %d", w.Code)
	}

	confirmReq := bookings.ConfirmBookingRequest{}
	w = makeUserRequest(mw.RequireAdmin(http.HandlerFunc(bookings.ConfirmBookingHandler)), "POST", "/api/admin/bookings/"+bookingID+"/confirm", confirmReq, ctx)
	if w.Code != http.StatusForbidden {
		t.Errorf("CONFIRM: expected status 403, got %d", w.Code)
	}

	w = makeUserRequest(mw.RequireAdmin(http.HandlerFunc(bookings.AdminCancelBookingHandler)), "POST", "/api/admin/bookings/"+bookingID+"/cancel", nil, ctx)
	if w.Code != http.StatusForbidden {
		t.Errorf("CANCEL: expected status 403, got %d", w.Code)
	}
}

// =============================================================================
// Admin Booking Holiday Conflict Tests
// =============================================================================

// TestAdminBookings_Create_DuringHolidayHours_Rejected verifies that an admin cannot create a booking
// during hours marked as closed in the exceptional working hours (holiday) system.
func TestAdminBookings_Create_DuringHolidayHours_Rejected(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)
	}

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

	// Create an exceptional (holiday) hours group for a fixed date (Thursday)
	// Using absolute date - no timezone conversions
	targetDate := time.Date(2026, 2, 26, 0, 0, 0, 0, time.UTC) // Thursday Feb 26, 2026
	var groupID int
	err = tx.QueryRow(ctx, `
		INSERT INTO exceptional_working_hours_groups (name, description)
		VALUES ($1, $2)
		RETURNING id
	`, "Holiday Closure", "Test holiday").Scan(&groupID)
	if err != nil {
		t.Fatalf("failed to create holiday group: %v", err)
	}

	// Add closed hours for targetDate (closed all day)
	// DB convention: 0=Monday..6=Sunday; Go: 0=Sunday..6=Saturday. Convert.
	dbWeekday := (int(targetDate.Weekday()) + 6) % 7
	_, err = tx.Exec(ctx, `
		INSERT INTO exceptional_working_hours (group_id, weekday, start_time, end_time, is_open)
		VALUES ($1, $2, $3, $4, $5)
	`, groupID, dbWeekday, "00:00:00", "23:59:59", false)
	if err != nil {
		t.Fatalf("failed to create holiday hours: %v", err)
	}

	// Apply the group to the week containing targetDate
	// Must use Monday of that week (matching handler logic)
	daysToMonday := int(targetDate.Weekday())
	if daysToMonday == 0 {
		daysToMonday = 7
	}
	mondayOfWeek := targetDate.AddDate(0, 0, -daysToMonday+1)
	_, err = tx.Exec(ctx, `
		INSERT INTO exceptional_group_applications (group_id, week_start)
		VALUES ($1, $2)
	`, groupID, mondayOfWeek)

	if err != nil {
		t.Fatalf("failed to create holiday application: %v", err)
	}

	// Try to create booking during holiday - should fail
	targetTime := targetDate.Add(14 * time.Hour).Truncate(time.Second) // 2 PM on targetDate
	req := bookings.AdminCreateBookingForUserRequest{
		UserID:     userID,
		StartTime:  targetTime,
		ServiceIDs: []string{serviceID},
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

	// Should be rejected (400 or 409 depending on implementation)
	if w.Code == http.StatusCreated {
		t.Errorf("expected booking to be rejected during holiday hours, but got 201")
	}
}

// =============================================================================
// Search Edge Case Tests
// =============================================================================

// TestAdminBookings_Search_CaseInsensitive verifies that the admin booking search is case-insensitive,
// matching booking notes regardless of uppercase/lowercase differences.
func TestAdminBookings_Search_CaseInsensitive(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)
	}

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

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

	// Update notes with mixed case
	_, err = tx.Exec(ctx,
		"UPDATE bookings SET notes = 'TestBooking With MixedCase' WHERE id = $1", bookingID)
	if err != nil {
		t.Fatalf("failed to update booking notes: %v", err)
	}

	handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler)

	// Test 1: Uppercase search should find mixed case notes
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=TESTBOOKING", nil, ctx)
	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", w.Code)
	}
	var resp bookings.BookingListResponse
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}
	if len(resp.Bookings) != 1 {
		t.Errorf("expected 1 booking for uppercase search, got %d", len(resp.Bookings))
	}

	// Test 2: Lowercase search should also find
	w = makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=testbooking", nil, ctx)
	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", w.Code)
	}
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}
	if len(resp.Bookings) != 1 {
		t.Errorf("expected 1 booking for lowercase search, got %d", len(resp.Bookings))
	}
}

// TestAdminBookings_Search_NoResults verifies that searching with a query that matches no bookings
// returns an empty list with total count of 0.
func TestAdminBookings_Search_NoResults(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)
	}

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

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

	handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler)

	// Search with non-matching query
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=NONEXISTENT_QUERY_XYZ123", nil, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200 for no results, got %d", w.Code)
	}

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

	if len(resp.Bookings) != 0 {
		t.Errorf("expected 0 bookings for non-matching search, got %d", len(resp.Bookings))
	}

	if resp.Total != 0 {
		t.Errorf("expected total 0 for non-matching search, got %d", resp.Total)
	}
}

// TestAdminBookings_Search_MultipleResults verifies that search returns all bookings whose notes
// contain the search query, with correct total count.
func TestAdminBookings_Search_MultipleResults(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)
	}

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

	// Create multiple bookings with similar notes
	bookingID1, err := fixtures.CreateTestBooking(tx, userID, serviceID)
	if err != nil {
		t.Fatalf("failed to create test booking 1: %v", err)
	}

	bookingID2, err := fixtures.CreateTestBooking(tx, userID, serviceID)
	if err != nil {
		t.Fatalf("failed to create test booking 2: %v", err)
	}

	// Update both with searchable notes
	_, err = tx.Exec(ctx,
		"UPDATE bookings SET notes = 'Search Query Pattern' WHERE id = $1", bookingID1)
	if err != nil {
		t.Fatalf("failed to update booking 1: %v", err)
	}

	_, err = tx.Exec(ctx,
		"UPDATE bookings SET notes = 'Another Search Query' WHERE id = $1", bookingID2)
	if err != nil {
		t.Fatalf("failed to update booking 2: %v", err)
	}

	handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler)

	// Search for "Query" - should match both
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=Query", nil, ctx)

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

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

	if len(resp.Bookings) != 2 {
		t.Errorf("expected 2 bookings matching 'Query', got %d", len(resp.Bookings))
	}

	if resp.Total != 2 {
		t.Errorf("expected total 2, got %d", resp.Total)
	}
}

// =============================================================================
// SearchAdminBookingsHandler — out_of_hours field
// =============================================================================

func TestAdminBookings_Search_OutOfHoursField(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)
	}

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

	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	var bookingID string
	err = tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status, out_of_hours, notes)
		VALUES ($1, $2, 'confirmed', true, 'OutOfHoursSearchTest')
		RETURNING id
	`, userID, futureTime).Scan(&bookingID)
	if err != nil {
		t.Fatalf("failed to create out-of-hours 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: %v", err)
	}

	handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=OutOfHoursSearchTest", nil, ctx)

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

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

	if len(resp.Bookings) == 0 {
		t.Fatal("expected at least 1 booking in search results")
	}

	var found bool
	for _, b := range resp.Bookings {
		if b.ID == bookingID {
			if !b.OutOfHours {
				t.Error("expected out_of_hours=true for the out-of-hours booking in search results")
			}
			found = true
			break
		}
	}
	if !found {
		t.Error("expected out-of-hours booking to appear in search results")
	}
}

func TestAdminBookings_Search_OutOfHoursFalseByDefault(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)
	}

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

	var bookingID string
	err = tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status, notes)
		VALUES ($1, $2, 'confirmed', 'NormalSearchBooking')
		RETURNING id
	`, userID, clock.Now().Add(72*time.Hour).Truncate(time.Second)).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: %v", err)
	}

	handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=NormalSearchBooking", nil, ctx)

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

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

	if len(resp.Bookings) == 0 {
		t.Fatal("expected at least 1 booking in search results")
	}

	var found bool
	for _, b := range resp.Bookings {
		if b.ID == bookingID {
			if b.OutOfHours {
				t.Error("expected out_of_hours=false (default) for normal booking in search results")
			}
			found = true
			break
		}
	}
	if !found {
		t.Error("expected normal booking to appear in search results")
	}
}

// =============================================================================
// Admin List Edit Requests Tests
// =============================================================================

// TestAdminBookings_ListEditRequests verifies that an admin can list all pending edit requests
// for a specific booking, and that the response includes the correct count and request details.
func TestAdminBookings_ListEditRequests(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)
	}

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

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

	// Update booking status to confirmed (required for edit requests)
	_, err = tx.Exec(ctx,
		"UPDATE bookings SET status = 'confirmed' WHERE id = $1", bookingID)
	if err != nil {
		t.Fatalf("failed to update booking status: %v", err)
	}

	// Clean up ALL existing edit requests in DB to ensure clean state
	_, err = tx.Exec(ctx, "DELETE FROM booking_edit_requests")
	if err != nil {
		t.Fatalf("failed to clean up edit requests: %v", err)
	}

	// Create 3 edit requests via direct SQL insert
	var emptyServices []string
	for i := 1; i <= 3; i++ {
		_, err = tx.Exec(ctx, `
			INSERT INTO booking_edit_requests (booking_id, requested_by, new_start_time, new_services, notes, has_overrides)
			VALUES ($1, $2, $3, $4, $5, $6)`,
			bookingID, userID, clock.Now().Add(time.Duration(i)*24*time.Hour),
			emptyServices, fmt.Sprintf("Edit request %d", i), false)
		if err != nil {
			t.Fatalf("failed to create edit request %d: %v", i, err)
		}
	}

	handler := http.HandlerFunc(bookings.AdminListEditRequestsHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/"+bookingID+"/edit-requests", nil, ctx)

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

	var resp struct {
		Requests []bookings.BookingEditRequest `json:"requests"`
		Total    int                           `json:"total"`
	}
	if err := parseResponseBody(w, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	if len(resp.Requests) != 3 {
		t.Errorf("expected 3 edit requests, got %d", len(resp.Requests))
	}

	if resp.Total != 3 {
		t.Errorf("expected total 3, got %d", resp.Total)
	}

	// Verify the requests are for the correct booking
	for _, req := range resp.Requests {
		if req.BookingID != bookingID {
			t.Errorf("expected booking ID %s, got %s", bookingID, req.BookingID)
		}
	}
}

// =============================================================================
// Admin Deny Edit Request Tests
// =============================================================================

// TestAdminBookings_DenyEditRequest verifies that denying an edit request deletes the request
// while keeping the original booking time unchanged, and returns success.
func TestAdminBookings_DenyEditRequest(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	adminID, 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)
	}

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

	// Create confirmed booking
	bookingID, err := fixtures.CreateTestBooking(tx, userID, serviceID)
	if err != nil {
		t.Fatalf("failed to create test booking: %v", err)
	}

	// Confirm the booking
	_, err = tx.Exec(ctx, "UPDATE bookings SET status = 'confirmed' WHERE id = $1", bookingID)
	if err != nil {
		t.Fatalf("failed to confirm booking: %v", err)
	}

	// Get original start_time
	var originalStartTime time.Time
	err = tx.QueryRow(ctx,
		"SELECT start_time FROM bookings WHERE id = $1", bookingID).Scan(&originalStartTime)
	if err != nil {
		t.Fatalf("failed to get original start_time: %v", err)
	}

	// Create edit request with new_start_time via direct SQL
	newStartTime := originalStartTime.Add(24 * time.Hour).Truncate(time.Minute)
	var editRequestID string
	err = tx.QueryRow(ctx,
		`INSERT INTO booking_edit_requests (booking_id, requested_by, new_start_time, notes)
		VALUES ($1, $2, $3, 'Please change time')
		RETURNING id`,
		bookingID, userID, newStartTime).Scan(&editRequestID)
	if err != nil {
		t.Fatalf("failed to create edit request: %v", err)
	}

	// Call admin deny endpoint
	handler := http.HandlerFunc(bookings.AdminRejectEditRequestHandler)

	path := fmt.Sprintf("/api/admin/bookings/%s/edit-requests/%s/deny", bookingID, editRequestID)
	req := httptest.NewRequest("POST", path, nil)

	rctx := chi.NewRouteContext()
	rctx.URLParams.Add("id", bookingID)
	rctx.URLParams.Add("request_id", editRequestID)
	reqCtx := context.WithValue(ctx, chi.RouteCtxKey, rctx)
	reqCtx = context.WithValue(reqCtx, mw.UserIDKey, adminID)
	reqCtx = context.WithValue(reqCtx, mw.UserRoleKey, "admin")
	req = req.WithContext(reqCtx)

	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)

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

	// Verify edit request is deleted
	var erCount int
	err = tx.QueryRow(ctx,
		"SELECT COUNT(*) FROM booking_edit_requests WHERE id = $1", editRequestID).Scan(&erCount)
	if err != nil {
		t.Fatalf("failed to query edit requests: %v", err)
	}
	if erCount != 0 {
		t.Errorf("expected edit request to be deleted after deny, got %d", erCount)
	}

	// Verify booking start_time unchanged
	var finalStartTime time.Time
	err = tx.QueryRow(ctx,
		"SELECT start_time FROM bookings WHERE id = $1", bookingID).Scan(&finalStartTime)
	if err != nil {
		t.Fatalf("failed to get final start_time: %v", err)
	}
	if !originalStartTime.Equal(finalStartTime) {
		t.Errorf("expected booking start_time to remain %v, got %v", originalStartTime, finalStartTime)
	}
}

// TestAdminBookings_ApproveEditRequest verifies that approving an edit request updates the booking's
// start_time to the requested time, deletes the edit request, and acknowledges the admin notification.
func TestAdminBookings_ApproveEditRequest(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

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

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

	// Set deposits_required=0
	_, err = tx.Exec(ctx, "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
	if err != nil {
		t.Fatalf("failed to set deposits_required: %v", err)
	}

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

	// Create confirmed booking
	bookingID, err := fixtures.CreateTestBooking(tx, userID, serviceID)
	if err != nil {
		t.Fatalf("failed to create test booking: %v", err)
	}

	// Confirm the booking
	_, err = tx.Exec(ctx,
		"UPDATE bookings SET status = $1 WHERE id = $2", "confirmed", bookingID)
	if err != nil {
		t.Fatalf("failed to confirm booking: %v", err)
	}

	// Get original booking start_time
	var originalStartTime time.Time
	err = tx.QueryRow(ctx,
		"SELECT start_time FROM bookings WHERE id = $1", bookingID).Scan(&originalStartTime)
	if err != nil {
		t.Fatalf("failed to get original start_time: %v", err)
	}

	// Create edit request with new_start_time
	newStartTime := clock.Now().Add(48 * time.Hour).Truncate(time.Minute)
	var editRequestID string
	var emptyServices []string
	err = tx.QueryRow(ctx,
		`INSERT INTO booking_edit_requests (booking_id, requested_by, new_start_time, new_services, notes)
		VALUES ($1, $2, $3, $4, 'Please change time')
		RETURNING id`,
		bookingID, userID, newStartTime, emptyServices).Scan(&editRequestID)
	if err != nil {
		t.Fatalf("failed to create edit request: %v", err)
	}

	// Create admin notification
	_, err = tx.Exec(ctx,
		`INSERT INTO admin_notifications (reason, booking_id, user_id)
		VALUES ('edit_requested', $1, $2)`,
		bookingID, userID)
	if err != nil {
		t.Fatalf("failed to create admin notification: %v", err)
	}

	// Call admin approve endpoint
	handler := http.HandlerFunc(bookings.AdminApproveEditRequestHandler)

	path := fmt.Sprintf("/api/admin/bookings/%s/edit-requests/%s/approve", bookingID, editRequestID)
	w := makeAdminRequest(handler, "POST", path, nil, ctx)

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

	// Verify edit request was deleted (approved)
	var erCount int
	err = tx.QueryRow(ctx,
		"SELECT COUNT(*) FROM booking_edit_requests WHERE id = $1", editRequestID).Scan(&erCount)
	if err != nil {
		t.Fatalf("failed to query edit requests: %v", err)
	}
	if erCount != 0 {
		t.Errorf("expected 0 edit requests after approve, got %d", erCount)
	}

	// Verify booking start_time was updated to new_start_time
	var updatedStartTime time.Time
	err = tx.QueryRow(ctx,
		"SELECT start_time FROM bookings WHERE id = $1", bookingID).Scan(&updatedStartTime)
	if err != nil {
		t.Fatalf("failed to query booking: %v", err)
	}
	if !updatedStartTime.Equal(newStartTime) {
		t.Errorf("expected booking start_time %v, got %v", newStartTime, updatedStartTime)
	}

	// Verify admin notification was acknowledged
	var ackTime *time.Time
	err = tx.QueryRow(ctx,
		`SELECT acknowledged_at FROM admin_notifications
		WHERE booking_id = $1 AND reason = 'edit_requested'`,
		bookingID).Scan(&ackTime)
	if err != nil {
		t.Fatalf("failed to query notification: %v", err)
	}
	if ackTime == nil {
		t.Errorf("expected notification to be acknowledged after approve, but acknowledged_at is still NULL")
	}
}

// =============================================================================
// Deposit System Tests
// =============================================================================

// TestAdminBookings_Get_DepositFields verifies that admin booking endpoints return
// deposit-related fields (deposit_required, deposit_amount, deposit_paid, deposit_deadline)
// for bookings that have deposit_required=true.
func TestAdminBookings_Get_DepositFields(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)
	}

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

	// Create booking via SQL with deposit_required=true (simulating user-created booking)
	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	var bookingID string
	err = tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status, deposit_required)
		VALUES ($1, $2, 'confirmed', true)
		RETURNING id
	`, userID, futureTime).Scan(&bookingID)
	if err != nil {
		t.Fatalf("failed to create booking: %v", err)
	}

	// Link service to booking
	_, 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: %v", err)
	}

	// GET single booking via admin endpoint
	w := makeAdminRequest(http.HandlerFunc(bookings.GetAdminBookingHandler), "GET", "/api/admin/bookings/"+bookingID, nil, ctx)

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

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

	// Verify deposit fields are populated
	if !fetchedBooking.DepositRequired {
		t.Error("expected DepositRequired to be true")
	}
	if fetchedBooking.DepositAmount <= 0 {
		t.Error("expected DepositAmount to be positive")
	}
	// DepositPaid is a bool, just verify it exists
	_ = fetchedBooking.DepositPaid
	if fetchedBooking.DepositDeadline == nil {
		t.Error("expected DepositDeadline to be set")
	}
}

// TestAdminBookings_List_DepositFields verifies that admin booking list returns
// deposit-related fields for each booking.
func TestAdminBookings_List_DepositFields(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)
	}

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

	// Create booking via SQL with deposit_required=true
	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	var bookingID string
	err = tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status, deposit_required)
		VALUES ($1, $2, 'pending', true)
		RETURNING id
	`, userID, futureTime).Scan(&bookingID)
	if err != nil {
		t.Fatalf("failed to create booking: %v", err)
	}

	// Link service to booking
	_, 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: %v", err)
	}

	// GET all bookings via admin endpoint
	w := makeAdminRequest(http.HandlerFunc(bookings.GetAllAdminBookingsHandler), "GET", "/api/admin/bookings", nil, ctx)

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

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

	if len(resp.Bookings) != 1 {
		t.Fatalf("expected 1 booking, got %d", len(resp.Bookings))
	}

	booking := resp.Bookings[0]

	// Verify deposit fields are populated in list
	if !booking.DepositRequired {
		t.Error("expected DepositRequired to be true in list")
	}
	if booking.DepositAmount <= 0 {
		t.Error("expected DepositAmount to be positive in list")
	}
}

// =============================================================================
// Time Blocker Tests for Admin Bookings
// =============================================================================

// TestAdminBookings_Create_OverlappingBlocker_WithWarning verifies that admins can
// create bookings that overlap with time blockers, but receive a warning.
// The booking is still created (201 Created), unlike regular users who get 409.
func TestAdminBookings_Create_OverlappingBlocker_WithWarning(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	adminID, 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)
	}

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

	// Create a time blocker for a specific time
	blockerTime := time.Date(2099, 12, 31, 10, 0, 0, 0, time.UTC)
	_, err = tx.Exec(ctx, `
		INSERT INTO time_blockers (start_time, duration_minutes, description, created_by)
		VALUES ($1, 60, 'Staff meeting', $2)
	`, blockerTime, adminID)
	if err != nil {
		t.Fatalf("failed to create time blocker: %v", err)
	}

	// Admin creates booking overlapping the blocker
	req := bookings.AdminCreateBookingForUserRequest{
		UserID:     userID,
		StartTime:  blockerTime,
		ServiceIDs: []string{serviceID},
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

	// Admin should get 201 Created (not 409 Conflict)
	if w.Code != http.StatusCreated {
		t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
	}

	// Parse response to check for warnings
	var response map[string]interface{}
	if err := parseResponseBody(w, &response); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	// Check for warnings array
	warnings, ok := response["warnings"].([]interface{})
	if !ok || len(warnings) == 0 {
		t.Error("expected warnings array with at least one warning")
	} else {
		// Verify warning mentions the blocker
		warningStr, ok := warnings[0].(string)
		if !ok {
			t.Errorf("expected warning to be a string, got: %v", warnings[0])
		} else if !bytes.Contains([]byte(warningStr), []byte("blocker")) {
			t.Errorf("expected warning to mention 'blocker', got: %s", warningStr)
		}
	}

	// Verify booking was created
	bookingData, ok := response["booking"].(map[string]interface{})
	if !ok {
		t.Fatal("expected booking in response")
	}
	if bookingData["id"] == nil {
		t.Error("expected booking ID to be set")
	}
}

// TestAdminBookings_Edit_OverlappingBlocker_WithWarning verifies that admins can
// edit bookings to overlap with time blockers, but receive a warning.
// The booking is still updated (200 OK with warnings), unlike regular users who get 409.

// TestAdminBookings_Create_EnforceDeposits_Bypass tests that admin can create bookings
// for users with outstanding deposits by setting enforce_deposits=false.
func TestAdminBookings_Create_EnforceDeposits_Bypass(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)
	}

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

	// Set user to have outstanding deposits (deposits_required = 3)
	_, err = tx.Exec(ctx, "UPDATE users SET deposits_required = 3 WHERE id = $1", userID)
	if err != nil {
		t.Fatalf("failed to set deposits_required: %v", err)
	}

	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	falseVal := false
	req := bookings.AdminCreateBookingForUserRequest{
		UserID:          userID,
		StartTime:       futureTime,
		ServiceIDs:      []string{serviceID},
		EnforceDeposits: &falseVal, // Bypass deposit check
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

	// Should succeed (not 409 Conflict) because deposits check was bypassed
	if w.Code != http.StatusCreated {
		t.Errorf("expected status 201 when enforce_deposits=false, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_Create_EnforceDeposits_Enforced tests that by default (or when enforce_deposits=true),
// admin bookings respect the deposit requirement rules.
func TestAdminBookings_Create_EnforceDeposits_Enforced(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)
	}

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

	// Set user to have outstanding deposits
	_, err = tx.Exec(ctx, "UPDATE users SET deposits_required = 3 WHERE id = $1", userID)
	if err != nil {
		t.Fatalf("failed to set deposits_required: %v", err)
	}

	// Create first booking for user (will have it active)
	firstTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	firstReq := bookings.AdminCreateBookingForUserRequest{
		UserID:          userID,
		StartTime:       firstTime,
		ServiceIDs:      []string{serviceID},
		EnforceDeposits: nil, // Default: enforce deposits
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", firstReq, ctx)

	if w.Code != http.StatusCreated {
		t.Fatalf("failed to create first booking: %d. body: %s", w.Code, w.Body.String())
	}

	// Try to create second booking (should fail due to one-active-booking limit)
	secondTime := clock.Now().Add(96 * time.Hour).Truncate(time.Second)
	secondReq := bookings.AdminCreateBookingForUserRequest{
		UserID:          userID,
		StartTime:       secondTime,
		ServiceIDs:      []string{serviceID},
		EnforceDeposits: nil, // Default: enforce (deposits_required > 0 still active)
	}

	w = makeAdminRequest(handler, "POST", "/api/admin/bookings", secondReq, ctx)

	// Should get 409 Conflict because user has active booking and deposits outstanding
	if w.Code != http.StatusConflict {
		t.Errorf("expected status 409 when enforce_deposits is enforced, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_Create_WalkIn tests that admins can create walk-in bookings
// (no advance time requirement), including immediate/past times if needed.
func TestAdminBookings_Create_WalkIn(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)
	}

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

	// Seed working hours so we have a valid booking window

	// Try to create booking with walk-in time (30 minutes from now - less than 1h requirement)
	// Regular users would be rejected, but admin should succeed
	walkInTime := clock.Now().Add(30 * time.Minute).Truncate(time.Second)
	walkInTime = time.Date(walkInTime.Year(), walkInTime.Month(), walkInTime.Day(), 10, 0, 0, 0, walkInTime.Location())

	req := bookings.AdminCreateBookingForUserRequest{
		UserID:     userID,
		StartTime:  walkInTime,
		ServiceIDs: []string{serviceID},
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

	// Should succeed - admins can bypass the 1-hour minimum requirement
	if w.Code != http.StatusCreated {
		t.Errorf("expected status 201 for admin walk-in, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_Create_WalkInWithDeposits tests that admins can create walk-ins
// even when user has outstanding deposits and enforce_deposits=false.
func TestAdminBookings_Create_WalkInWithDeposits(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)
	}

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

	// Seed working hours

	// Set user to have outstanding deposits
	_, err = tx.Exec(ctx, "UPDATE users SET deposits_required = 3 WHERE id = $1", userID)
	if err != nil {
		t.Fatalf("failed to set deposits: %v", err)
	}

	// Create walk-in booking with enforce_deposits=false
	walkInTime := clock.Now().Add(15 * time.Minute).Truncate(time.Second)
	walkInTime = time.Date(walkInTime.Year(), walkInTime.Month(), walkInTime.Day(), 10, 0, 0, 0, walkInTime.Location())

	falseVal := false
	req := bookings.AdminCreateBookingForUserRequest{
		UserID:          userID,
		StartTime:       walkInTime,
		ServiceIDs:      []string{serviceID},
		EnforceDeposits: &falseVal, // Bypass deposit checks
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

	// Should succeed - admin walk-in with deposit bypass
	if w.Code != http.StatusCreated {
		t.Errorf("expected status 201 for admin walk-in with deposits, got %d. body: %s", w.Code, w.Body.String())
	}
}

// =============================================================================
// Deposit Reduction Tests (Tasks 4, 6, 10)
// =============================================================================

// TestAdminBookings_Confirm_CompletesWithPayment_ReducesDeposits verifies that when a
// confirmed booking is progressed to completed with at least one payment, the user's
// deposits_required is reduced by 1.
func TestAdminBookings_Confirm_CompletesWithPayment_ReducesDeposits(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)
	}

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

	// Set user to have deposits_required = 2
	_, err = tx.Exec(ctx, "UPDATE users SET deposits_required = 2 WHERE id = $1", userID)
	if err != nil {
		t.Fatalf("failed to set deposits_required: %v", err)
	}

	// Create confirmed booking
	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	var bookingID string
	err = tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status)
		VALUES ($1, $2, 'confirmed')
		RETURNING id
	`, userID, futureTime).Scan(&bookingID)
	if err != nil {
		t.Fatalf("failed to create booking: %v", err)
	}

	// Link service to booking
	_, 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: %v", err)
	}

	// Add a payment for the booking
	_, err = tx.Exec(ctx, `
		INSERT INTO payments (booking_id, amount, payment_type, payment_method, status)
		VALUES ($1, 50.00, 'deposit', 'online_square', 'completed')
	`, bookingID)
	if err != nil {
		t.Fatalf("failed to create payment: %v", err)
	}

	// Progress booking to completed via admin progress handler
	progressReq := bookings.ProgressBookingRequest{
		Status: "completed",
	}

	handler := http.HandlerFunc(bookings.ProgressBookingHandler)
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID+"/progress", progressReq, ctx)

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

	// Verify deposits_required was reduced from 2 to 1
	var depositsRequired int
	err = tx.QueryRow(ctx, "SELECT deposits_required FROM users WHERE id = $1", userID).Scan(&depositsRequired)
	if err != nil {
		t.Fatalf("failed to query deposits_required: %v", err)
	}
	if depositsRequired != 1 {
		t.Errorf("expected deposits_required = 1 after completing booking with payment, got %d", depositsRequired)
	}
}

// TestAdminBookings_Confirm_CompletesWithoutPayment_NoReduction verifies that when a
// booking is completed without any payments, the user's deposits_required is NOT reduced.
func TestAdminBookings_Confirm_CompletesWithoutPayment_NoReduction(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)
	}

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

	// Set user to have deposits_required = 2
	_, err = tx.Exec(ctx, "UPDATE users SET deposits_required = 2 WHERE id = $1", userID)
	if err != nil {
		t.Fatalf("failed to set deposits_required: %v", err)
	}

	// Create confirmed booking
	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	var bookingID string
	err = tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status)
		VALUES ($1, $2, 'confirmed')
		RETURNING id
	`, userID, futureTime).Scan(&bookingID)
	if err != nil {
		t.Fatalf("failed to create booking: %v", err)
	}

	// Link service to booking
	_, 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: %v", err)
	}

	// Note: NO payment added - this is the key difference

	// Progress booking to completed via admin progress handler
	progressReq := bookings.ProgressBookingRequest{
		Status: "completed",
	}

	handler := http.HandlerFunc(bookings.ProgressBookingHandler)
	w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID+"/progress", progressReq, ctx)

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

	// Verify deposits_required is still 2 (no reduction because no payment)
	var depositsRequired int
	err = tx.QueryRow(ctx, "SELECT deposits_required FROM users WHERE id = $1", userID).Scan(&depositsRequired)
	if err != nil {
		t.Fatalf("failed to query deposits_required: %v", err)
	}
	if depositsRequired != 2 {
		t.Errorf("expected deposits_required = 2 (unchanged) after completing booking without payment, got %d", depositsRequired)
	}
}

// TestAdminBookings_Create_EnforceDepositsFalse_BypassesLimit verifies that when
// enforce_deposits is set to false, the admin can create a second booking for a user
// who already has an active booking, bypassing the one-active-booking limit.
func TestAdminBookings_Create_EnforceDepositsFalse_BypassesLimit(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)
	}

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

	// Set user to have deposits_required = 3
	_, err = tx.Exec(ctx, "UPDATE users SET deposits_required = 3 WHERE id = $1", userID)
	if err != nil {
		t.Fatalf("failed to set deposits_required: %v", err)
	}

	// Create first booking (will be active)
	firstTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	firstReq := bookings.AdminCreateBookingForUserRequest{
		UserID:     userID,
		StartTime:  firstTime,
		ServiceIDs: []string{serviceID},
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", firstReq, ctx)

	if w.Code != http.StatusCreated {
		t.Fatalf("failed to create first booking: %d. body: %s", w.Code, w.Body.String())
	}

	// Try to create second booking with enforce_deposits=false
	secondTime := clock.Now().Add(96 * time.Hour).Truncate(time.Second)
	falseVal := false
	secondReq := bookings.AdminCreateBookingForUserRequest{
		UserID:          userID,
		StartTime:       secondTime,
		ServiceIDs:      []string{serviceID},
		EnforceDeposits: &falseVal, // Bypass deposit checks
	}

	w = makeAdminRequest(handler, "POST", "/api/admin/bookings", secondReq, ctx)

	// Should succeed (201 Created) because enforce_deposits=false bypasses the limit
	if w.Code != http.StatusCreated {
		t.Errorf("expected status 201 when enforce_deposits=false, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_Create_EnforceDepositsFalse_Within24h verifies that when
// enforce_deposits is set to false, the admin can create a booking within 24 hours
// for a user with outstanding deposits.
func TestAdminBookings_Create_EnforceDepositsFalse_Within24h(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)
	}

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

	// Seed working hours

	// Set user to have deposits_required = 3
	_, err = tx.Exec(ctx, "UPDATE users SET deposits_required = 3 WHERE id = $1", userID)
	if err != nil {
		t.Fatalf("failed to set deposits_required: %v", err)
	}

	// Try to create booking within 24 hours with enforce_deposits=false
	// Use a time 12 hours from now (within 24h)
	within24h := clock.Now().Add(12 * time.Hour).Truncate(time.Second)
	// Adjust to a valid slot within working hours
	within24h = time.Date(within24h.Year(), within24h.Month(), within24h.Day(), 14, 0, 0, 0, within24h.Location())

	falseVal := false
	req := bookings.AdminCreateBookingForUserRequest{
		UserID:          userID,
		StartTime:       within24h,
		ServiceIDs:      []string{serviceID},
		EnforceDeposits: &falseVal, // Bypass deposit checks
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

	// Should succeed (201 Created) because enforce_deposits=false bypasses the 24h check
	if w.Code != http.StatusCreated {
		t.Errorf("expected status 201 when enforce_deposits=false for within-24h booking, got %d. body: %s", w.Code, w.Body.String())
	}
}

// TestAdminBookings_Create_WalkInGuestUser verifies that an admin can create a booking
// for a guest user (created via fixtures.CreateTestGuestUser).
func TestAdminBookings_Create_WalkInGuestUser(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)
	}

	guestID, err := fixtures.CreateTestGuestUser(tx)
	if err != nil {
		t.Fatalf("failed to create guest user: %v", err)
	}

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

	// Seed working hours

	// Create booking for tomorrow
	tomorrow := clock.Now().Add(24 * time.Hour).Truncate(time.Second)
	tomorrow = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 10, 0, 0, 0, tomorrow.Location())

	req := bookings.AdminCreateBookingForUserRequest{
		UserID:     guestID,
		StartTime:  tomorrow,
		ServiceIDs: []string{serviceID},
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

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

	// Verify booking exists with correct user_id
	var foundUserID string
	err = tx.QueryRow(ctx, `
		SELECT user_id FROM bookings WHERE user_id = $1 ORDER BY created_at DESC LIMIT 1
	`, guestID).Scan(&foundUserID)
	if err != nil {
		t.Fatalf("failed to verify booking exists: %v", err)
	}
	if foundUserID != guestID {
		t.Errorf("expected booking user_id = %s, got %s", guestID, foundUserID)
	}
}

// =============================================================================
// Exceptional Hours Tests
// =============================================================================

// TestAdminUpdateBooking_ClosedExceptionalHours_WarningOnly verifies that an admin can
// update a booking's time to fall within a closed exceptional hours period, receiving
// a warning but proceeding with the update.
func TestGetBookingsByCreatedRange(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)
	}

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

	// Create bookings with specific created_at timestamps
	_, err = tx.Exec(ctx, `
		INSERT INTO bookings (id, user_id, start_time, status, created_at)
		VALUES ('book00000001', $1, '2099-12-31 10:00:00+00', 'confirmed', '2025-01-15 09:00:00+00')
	`, userID)
	if err != nil {
		t.Fatalf("failed to create booking 1: %v", err)
	}
	_, err = tx.Exec(ctx, `
		INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000001', $1)
	`, serviceID)
	if err != nil {
		t.Fatalf("failed to link service to booking 1: %v", err)
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO bookings (id, user_id, start_time, status, created_at)
		VALUES ('book00000002', $1, '2099-12-31 11:00:00+00', 'pending', '2025-01-15 14:00:00+00')
	`, userID)
	if err != nil {
		t.Fatalf("failed to create booking 2: %v", err)
	}
	_, err = tx.Exec(ctx, `
		INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000002', $1)
	`, serviceID)
	if err != nil {
		t.Fatalf("failed to link service to booking 2: %v", err)
	}

	// Booking outside the range (created before)
	_, err = tx.Exec(ctx, `
		INSERT INTO bookings (id, user_id, start_time, status, created_at)
		VALUES ('book00000003', $1, '2099-12-31 12:00:00+00', 'confirmed', '2025-01-10 09:00:00+00')
	`, userID)
	if err != nil {
		t.Fatalf("failed to create booking 3: %v", err)
	}
	_, err = tx.Exec(ctx, `
		INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000003', $1)
	`, serviceID)
	if err != nil {
		t.Fatalf("failed to link service to booking 3: %v", err)
	}

	handler := http.HandlerFunc(bookings.GetBookingsByCreatedRangeHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?start=2025-01-15T00:00:00Z&end=2025-01-16T00:00:00Z", nil, ctx)

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

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

	if len(resp.Bookings) != 2 {
		t.Errorf("expected 2 bookings in range, got %d", len(resp.Bookings))
	}
}

// TestGetBookingsByCreatedRange_Empty verifies that the endpoint returns an
// empty array when no bookings fall within the created_at range.
func TestGetBookingsByCreatedRange_Empty(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)
	}

	handler := http.HandlerFunc(bookings.GetBookingsByCreatedRangeHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?start=2025-01-01T00:00:00Z&end=2025-01-02T00:00:00Z", nil, ctx)

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

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

	if len(resp.Bookings) != 0 {
		t.Errorf("expected 0 bookings, got %d", len(resp.Bookings))
	}
}

// TestGetBookingsByCreatedRange_MissingParams verifies that the endpoint
// returns 400 when start or end query parameters are missing.
func TestGetBookingsByCreatedRange_MissingParams(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)
	}

	handler := http.HandlerFunc(bookings.GetBookingsByCreatedRangeHandler)

	// Missing both params
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range", nil, ctx)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400 (missing both), got %d", w.Code)
	}

	// Missing end param
	w = makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?start=2025-01-01T00:00:00Z", nil, ctx)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400 (missing end), got %d", w.Code)
	}

	// Missing start param
	w = makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?end=2025-01-02T00:00:00Z", nil, ctx)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400 (missing start), got %d", w.Code)
	}
}

// TestGetBookingsByCreatedRange_InvalidFormat verifies that the endpoint
// returns 400 when the date format is invalid.
func TestGetBookingsByCreatedRange_InvalidFormat(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)
	}

	handler := http.HandlerFunc(bookings.GetBookingsByCreatedRangeHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?start=not-a-date&end=2025-01-02T00:00:00Z", nil, ctx)

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

// TestGetBookingsByCreatedRange_OrderedByCreatedAt verifies that results
// are returned in ascending order by created_at.
func TestGetBookingsByCreatedRange_OrderedByCreatedAt(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)
	}

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

	// Create bookings with created_at in reverse order
	_, err = tx.Exec(ctx, `
		INSERT INTO bookings (id, user_id, start_time, status, created_at)
		VALUES ('book00000010', $1, '2099-12-31 10:00:00+00', 'confirmed', '2025-03-01 15:00:00+00')
	`, userID)
	if err != nil {
		t.Fatalf("failed to create booking 10: %v", err)
	}
	_, err = tx.Exec(ctx, `
		INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000010', $1)
	`, serviceID)
	if err != nil {
		t.Fatalf("failed to link service to booking 10: %v", err)
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO bookings (id, user_id, start_time, status, created_at)
		VALUES ('book00000011', $1, '2099-12-31 11:00:00+00', 'pending', '2025-03-01 10:00:00+00')
	`, userID)
	if err != nil {
		t.Fatalf("failed to create booking 11: %v", err)
	}
	_, err = tx.Exec(ctx, `
		INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000011', $1)
	`, serviceID)
	if err != nil {
		t.Fatalf("failed to link service to booking 11: %v", err)
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO bookings (id, user_id, start_time, status, created_at)
		VALUES ('book00000012', $1, '2099-12-31 12:00:00+00', 'confirmed', '2025-03-01 12:00:00+00')
	`, userID)
	if err != nil {
		t.Fatalf("failed to create booking 12: %v", err)
	}
	_, err = tx.Exec(ctx, `
		INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000012', $1)
	`, serviceID)
	if err != nil {
		t.Fatalf("failed to link service to booking 12: %v", err)
	}

	handler := http.HandlerFunc(bookings.GetBookingsByCreatedRangeHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?start=2025-03-01T00:00:00Z&end=2025-03-02T00:00:00Z", nil, ctx)

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

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

	if len(resp.Bookings) != 3 {
		t.Fatalf("expected 3 bookings, got %d", len(resp.Bookings))
	}

	// Verify order: book00000011 (10:00) < book00000012 (12:00) < book00000010 (15:00)
	expectedOrder := []string{"book00000011", "book00000012", "book00000010"}
	for i, expected := range expectedOrder {
		if resp.Bookings[i].ID != expected {
			t.Errorf("booking[%d] expected %s, got %s", i, expected, resp.Bookings[i].ID)
		}
	}
}

func TestGetAdminBooking_WithDiscounts(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

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

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

	// Create completed booking
	bookingTime := clock.Now().Add(-24 * time.Hour)
	bookingID := createCompletedBookingWithTimeForAdmin(t, ctx, tx, userID, serviceID, bookingTime, 50.00)

	// Create a discount campaign and apply it
	var campaignID string
	err = tx.QueryRow(ctx, `
		INSERT INTO discount_campaigns (name, campaign_type, discount_percent, status, start_date, end_date)
		VALUES ($1, 'time_based', 10.0, 'active', NOW() - INTERVAL '2 days', NOW() + INTERVAL '2 days')
		RETURNING id
	`, "Admin Test Campaign").Scan(&campaignID)
	if err != nil {
		t.Fatalf("failed to create campaign: %v", err)
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO booking_discounts (booking_id, user_id, discount_source, source_id, campaign_type, discount_percent, original_total, discount_amount)
		VALUES ($1, $2, 'campaign', $3, 'time_based', 10.0, 50.00, 5.00)
	`, bookingID, userID, campaignID)
	if err != nil {
		t.Fatalf("failed to create booking discount: %v", err)
	}

	// Call GetAdminBookingHandler
	handler := http.HandlerFunc(bookings.GetAdminBookingHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/"+bookingID, nil, ctx)

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

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

	if len(booking.Discounts) != 1 {
		t.Fatalf("expected 1 discount, got %d", len(booking.Discounts))
	}

	d := booking.Discounts[0]
	if d.CampaignName == nil || *d.CampaignName != "Admin Test Campaign" {
		t.Errorf("expected campaign name 'Admin Test Campaign', got %v", d.CampaignName)
	}
	if d.DiscountAmount != 5.00 {
		t.Errorf("expected discount amount 5.00, got %.2f", d.DiscountAmount)
	}
}

func createCompletedBookingWithTimeForAdmin(t *testing.T, ctx context.Context, tx db.Querier, userID, serviceID string, startTime time.Time, price float64) string {
	t.Helper()
	var bookingID string
	err := tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status)
		VALUES ($1, $2, 'completed')
		RETURNING id
	`, userID, startTime).Scan(&bookingID)
	if err != nil {
		t.Fatalf("failed to create completed booking: %v", err)
	}

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

	return bookingID
}

// =============================================================================
// Custom Service Tests
// =============================================================================

// TestAdminBookings_CreateWithCustomServices verifies that an admin can create
// a booking with only custom services via AdminCreateBookingForUserHandler.
// It checks that the booking_custom_services entry is created and the custom
// service usage_count is incremented.
func TestAdminBookings_CreateWithCustomServices(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)
	}

	customServiceID, err := fixtures.CreateTestCustomService(tx)
	if err != nil {
		t.Fatalf("failed to create custom service: %v", err)
	}

	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	req := bookings.AdminCreateBookingForUserRequest{
		UserID:           userID,
		StartTime:        futureTime,
		CustomServiceIDs: []string{customServiceID},
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

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

	var response map[string]interface{}
	if err := parseResponseBody(w, &response); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	bookingData, ok := response["booking"].(map[string]interface{})
	if !ok {
		t.Fatal("expected booking in response")
	}

	bookingID, ok := bookingData["id"].(string)
	if !ok || bookingID == "" {
		t.Fatal("expected booking id in response")
	}

	// Verify booking_custom_services has the custom service linked
	var bcsCount int
	err = tx.QueryRow(ctx,
		"SELECT COUNT(*) FROM booking_custom_services WHERE booking_id = $1 AND custom_service_id = $2",
		bookingID, customServiceID).Scan(&bcsCount)
	if err != nil {
		t.Fatalf("failed to query booking_custom_services: %v", err)
	}
	if bcsCount != 1 {
		t.Errorf("expected 1 booking_custom_services entry, got %d", bcsCount)
	}

	// Verify custom_services usage_count was incremented
	var usageCount int
	err = tx.QueryRow(ctx,
		"SELECT usage_count FROM custom_services WHERE id = $1", customServiceID).Scan(&usageCount)
	if err != nil {
		t.Fatalf("failed to query custom_service usage_count: %v", err)
	}
	if usageCount < 1 {
		t.Errorf("expected usage_count >= 1, got %d", usageCount)
	}
}

// TestAdminBookings_CreateWithCustomAndRegularServices verifies that an admin
// can create a booking with both regular and custom services simultaneously.
// It checks that entries are created in both booking_services and
// booking_custom_services.
func TestAdminBookings_CreateWithCustomAndRegularServices(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)
	}

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

	customServiceID, err := fixtures.CreateTestCustomService(tx)
	if err != nil {
		t.Fatalf("failed to create custom service: %v", err)
	}

	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	req := bookings.AdminCreateBookingForUserRequest{
		UserID:           userID,
		StartTime:        futureTime,
		ServiceIDs:       []string{serviceID},
		CustomServiceIDs: []string{customServiceID},
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

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

	var response map[string]interface{}
	if err := parseResponseBody(w, &response); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	bookingData, ok := response["booking"].(map[string]interface{})
	if !ok {
		t.Fatal("expected booking in response")
	}

	bookingID, ok := bookingData["id"].(string)
	if !ok || bookingID == "" {
		t.Fatal("expected booking id in response")
	}

	// Verify booking_services has the regular service
	var svcCount int
	err = tx.QueryRow(ctx,
		"SELECT COUNT(*) FROM booking_services WHERE booking_id = $1 AND service_id = $2",
		bookingID, serviceID).Scan(&svcCount)
	if err != nil {
		t.Fatalf("failed to query booking_services: %v", err)
	}
	if svcCount != 1 {
		t.Errorf("expected 1 booking_services entry, got %d", svcCount)
	}

	// Verify booking_custom_services has the custom service
	var csCount int
	err = tx.QueryRow(ctx,
		"SELECT COUNT(*) FROM booking_custom_services WHERE booking_id = $1 AND custom_service_id = $2",
		bookingID, customServiceID).Scan(&csCount)
	if err != nil {
		t.Fatalf("failed to query booking_custom_services: %v", err)
	}
	if csCount != 1 {
		t.Errorf("expected 1 booking_custom_services entry, got %d", csCount)
	}
}

// TestAdminBookings_Create_CustomServiceValidation verifies that the handler
// rejects requests with neither service_ids nor custom_service_ids, testing
// both nil and empty arrays.
func TestAdminBookings_Create_CustomServiceValidation(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)
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)

	// Test 1: both service_ids and custom_service_ids are nil
	t.Run("missing both service and custom service IDs", func(t *testing.T) {
		req := bookings.AdminCreateBookingForUserRequest{
			UserID:    userID,
			StartTime: clock.Now().Add(72 * time.Hour),
		}
		w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)
		if w.Code != http.StatusBadRequest {
			t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
		}
	})

	// Test 2: both are empty arrays
	t.Run("empty service_ids and custom_service_ids arrays", func(t *testing.T) {
		req := bookings.AdminCreateBookingForUserRequest{
			UserID:           userID,
			StartTime:        clock.Now().Add(72 * time.Hour),
			ServiceIDs:       []string{},
			CustomServiceIDs: []string{},
		}
		w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)
		if w.Code != http.StatusBadRequest {
			t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
		}
	})

	// Test 3: custom_service_ids is provided (should succeed — need working hours)
	t.Run("provides custom_service_ids only", func(t *testing.T) {

		customServiceID, err := fixtures.CreateTestCustomService(tx)
		if err != nil {
			t.Fatalf("failed to create custom service: %v", err)
		}

		futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
		req := bookings.AdminCreateBookingForUserRequest{
			UserID:           userID,
			StartTime:        futureTime,
			CustomServiceIDs: []string{customServiceID},
		}
		w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)
		if w.Code != http.StatusCreated {
			t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
		}
	})
}

// TestAdminBookings_Confirm_WithCustomOverrides verifies that an admin can
// confirm a booking with custom service overrides via ConfirmBookingHandler.
// It checks that the override_price and override_duration_minutes are stored
// in booking_custom_services.
func TestAdminBookings_Confirm_WithCustomOverrides(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)
	}

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

	customServiceID, err := fixtures.CreateTestCustomService(tx)
	if err != nil {
		t.Fatalf("failed to create custom service: %v", err)
	}

	// Create a pending booking with a regular service
	bookingID, err := fixtures.CreateTestBooking(tx, userID, serviceID)
	if err != nil {
		t.Fatalf("failed to create test booking: %v", err)
	}

	// Link a custom service to the booking
	_, err = tx.Exec(ctx,
		"INSERT INTO booking_custom_services (booking_id, custom_service_id) VALUES ($1, $2)",
		bookingID, customServiceID)
	if err != nil {
		t.Fatalf("failed to link custom service to booking: %v", err)
	}

	// Confirm with custom service overrides
	overridePrice := 65.00
	overrideDuration := 30
	confirmReq := bookings.ConfirmBookingRequest{
		CustomServiceOverrides: []bookings.ServiceOverride{
			{
				ServiceID:               customServiceID,
				OverridePrice:           &overridePrice,
				OverrideDurationMinutes: &overrideDuration,
			},
		},
	}

	handler := http.HandlerFunc(bookings.ConfirmBookingHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/confirm", confirmReq, ctx)

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

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

	if booking.Status != "confirmed" {
		t.Errorf("expected status 'confirmed', got %s", booking.Status)
	}

	// Verify override was applied to booking_custom_services
	var actualPrice *float64
	var actualDuration *int
	err = tx.QueryRow(ctx,
		"SELECT override_price, override_duration_minutes FROM booking_custom_services WHERE booking_id = $1 AND custom_service_id = $2",
		bookingID, customServiceID).Scan(&actualPrice, &actualDuration)
	if err != nil {
		t.Fatalf("failed to query booking_custom_services override: %v", err)
	}
	if actualPrice == nil || *actualPrice != overridePrice {
		t.Errorf("expected override_price %.2f, got %v", overridePrice, actualPrice)
	}
	if actualDuration == nil || *actualDuration != overrideDuration {
		t.Errorf("expected override_duration_minutes %d, got %v", overrideDuration, actualDuration)
	}
}

// TestAdminBookings_CreateWithCustomServicesAndOverrides verifies that an admin
// can create a booking with custom services and apply price/duration overrides
// at creation time via AdminCreateBookingForUserHandler.
func TestAdminBookings_CreateWithCustomServicesAndOverrides(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)
	}

	customServiceID, err := fixtures.CreateTestCustomService(tx)
	if err != nil {
		t.Fatalf("failed to create custom service: %v", err)
	}

	overridePrice := 60.00
	overrideDuration := 30
	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	req := bookings.AdminCreateBookingForUserRequest{
		UserID:           userID,
		StartTime:        futureTime,
		CustomServiceIDs: []string{customServiceID},
		CustomOverrides: []bookings.ServiceOverride{
			{
				ServiceID:               customServiceID,
				OverridePrice:           &overridePrice,
				OverrideDurationMinutes: &overrideDuration,
			},
		},
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

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

	var response map[string]interface{}
	if err := parseResponseBody(w, &response); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	bookingData, ok := response["booking"].(map[string]interface{})
	if !ok {
		t.Fatal("expected booking in response")
	}

	bookingID, ok := bookingData["id"].(string)
	if !ok || bookingID == "" {
		t.Fatal("expected booking id in response")
	}

	// Verify override was applied to booking_custom_services
	var actualPrice *float64
	var actualDuration *int
	err = tx.QueryRow(ctx,
		"SELECT override_price, override_duration_minutes FROM booking_custom_services WHERE booking_id = $1 AND custom_service_id = $2",
		bookingID, customServiceID).Scan(&actualPrice, &actualDuration)
	if err != nil {
		t.Fatalf("failed to query booking_custom_services override: %v", err)
	}
	if actualPrice == nil || *actualPrice != overridePrice {
		t.Errorf("expected override_price %.2f, got %v", overridePrice, actualPrice)
	}
	if actualDuration == nil || *actualDuration != overrideDuration {
		t.Errorf("expected override_duration_minutes %d, got %v", overrideDuration, actualDuration)
	}
}

// TestAdminBookings_AdminReserve_WithCustomServices verifies that an admin can
// create a call-in reservation with custom services via AdminReserveSlotHandler.
// It verifies the response duration matches the custom service duration and
// that the time_blocker is created reflecting the custom service duration.
func TestAdminBookings_AdminReserve_WithCustomServices(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

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

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

	customServiceID, err := fixtures.CreateTestCustomService(tx)
	if err != nil {
		t.Fatalf("failed to create custom service: %v", err)
	}

	// Set custom service duration to a known value for assertion
	_, err = tx.Exec(ctx,
		"UPDATE custom_services SET duration_minutes = 45 WHERE id = $1", customServiceID)
	if err != nil {
		t.Fatalf("failed to update custom service duration: %v", err)
	}

	tomorrow := clock.Now().Add(24 * time.Hour).Truncate(time.Second)
	tomorrow = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 10, 0, 0, 0, tomorrow.Location())

	req := bookings.AdminReserveSlotRequest{
		UserID:           &userID,
		ReservationType:  "callin",
		StartTime:        tomorrow,
		CustomServiceIDs: []string{customServiceID},
		TTLMinutes:       15,
	}

	handler := http.HandlerFunc(bookings.AdminReserveSlotHandler)
	w := makeRequestWithContext(handler, "POST", "/api/admin/bookings/reserve", req, adminID, "admin", ctx)

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

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

	// Custom service duration is 45 minutes
	if resp.DurationMinutes != 45 {
		t.Errorf("expected duration 45, got %d", resp.DurationMinutes)
	}

	// Verify time_blocker was created with correct description pattern
	var desc string
	err = tx.QueryRow(ctx,
		"SELECT description FROM time_blockers WHERE description LIKE 'RESERVATION:admin:callin:%'",
	).Scan(&desc)
	if err != nil {
		t.Errorf("failed to query time_blocker: %v", err)
	}

	if !strings.HasPrefix(desc, "RESERVATION:admin:callin:") {
		t.Errorf("expected description to start with 'RESERVATION:admin:callin:', got %s", desc)
	}
}

// =============================================================================
// GetAdminBookingHandler — out_of_hours field
// =============================================================================

func TestGetAdminBooking_OutOfHoursField(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

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

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

	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	var bookingID string
	err = tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status, out_of_hours)
		VALUES ($1, $2, 'confirmed', true)
		RETURNING id
	`, userID, futureTime).Scan(&bookingID)
	if err != nil {
		t.Fatalf("failed to create out-of-hours 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: %v", err)
	}

	handler := http.HandlerFunc(bookings.GetAdminBookingHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/"+bookingID, nil, ctx)

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

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

	if !booking.OutOfHours {
		t.Error("expected out_of_hours=true in booking response")
	}
}

func TestGetAdminBooking_OutOfHoursFalseByDefault(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

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

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

	var bookingID string
	err = tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status)
		VALUES ($1, $2, 'confirmed')
		RETURNING id
	`, userID, clock.Now().Add(72*time.Hour).Truncate(time.Second)).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: %v", err)
	}

	handler := http.HandlerFunc(bookings.GetAdminBookingHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings/"+bookingID, nil, ctx)

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

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

	if booking.OutOfHours {
		t.Error("expected out_of_hours=false (default) for normal booking")
	}
}

// =============================================================================
// GetAllAdminBookingsHandler — out_of_hours field
// =============================================================================

func TestAdminBookings_List_OutOfHoursField(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)
	}

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

	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	var bookingID string
	err = tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status, out_of_hours)
		VALUES ($1, $2, 'confirmed', true)
		RETURNING id
	`, userID, futureTime).Scan(&bookingID)
	if err != nil {
		t.Fatalf("failed to create out-of-hours 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: %v", err)
	}

	handler := http.HandlerFunc(bookings.GetAllAdminBookingsHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings", nil, ctx)

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

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

	if len(resp.Bookings) == 0 {
		t.Fatal("expected at least 1 booking in list response")
	}

	var found bool
	for _, b := range resp.Bookings {
		if b.ID == bookingID {
			if !b.OutOfHours {
				t.Error("expected out_of_hours=true for the out-of-hours booking in list response")
			}
			found = true
			break
		}
	}
	if !found {
		t.Error("expected out-of-hours booking to appear in list response")
	}
}

func TestAdminBookings_List_OutOfHoursFalseByDefault(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)
	}

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

	var bookingID string
	err = tx.QueryRow(ctx, `
		INSERT INTO bookings (user_id, start_time, status)
		VALUES ($1, $2, 'confirmed')
		RETURNING id
	`, userID, clock.Now().Add(72*time.Hour).Truncate(time.Second)).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: %v", err)
	}

	handler := http.HandlerFunc(bookings.GetAllAdminBookingsHandler)
	w := makeAdminRequest(handler, "GET", "/api/admin/bookings", nil, ctx)

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

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

	if len(resp.Bookings) == 0 {
		t.Fatal("expected at least 1 booking in list response")
	}

	var found bool
	for _, b := range resp.Bookings {
		if b.ID == bookingID {
			if b.OutOfHours {
				t.Error("expected out_of_hours=false for normal booking in list response")
			}
			found = true
			break
		}
	}
	if !found {
		t.Error("expected normal booking to appear in list response")
	}
}

// =============================================================================
// AdminCreateBookingForUserHandler — out_of_hours field
// =============================================================================

func TestAdminBookings_Create_OutOfHours(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)
	}

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

	_, err = tx.Exec(ctx, "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
	if err != nil {
		t.Fatalf("failed to set deposits_required: %v", err)
	}

	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	req := bookings.AdminCreateBookingForUserRequest{
		UserID:     userID,
		StartTime:  futureTime,
		ServiceIDs: []string{serviceID},
		OutOfHours: true,
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

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

	var response map[string]interface{}
	if err := parseResponseBody(w, &response); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	bookingData, ok := response["booking"].(map[string]interface{})
	if !ok {
		t.Fatal("expected booking in response")
	}

	outOfHours, ok := bookingData["out_of_hours"].(bool)
	if !ok {
		t.Fatal("expected out_of_hours field in booking response")
	}
	if !outOfHours {
		t.Error("expected out_of_hours=true in create booking response")
	}

	bookingID, ok := bookingData["id"].(string)
	if !ok || bookingID == "" {
		t.Fatal("expected booking id in response")
	}
	var dbOutOfHours bool
	err = tx.QueryRow(ctx, "SELECT out_of_hours FROM bookings WHERE id = $1", bookingID).Scan(&dbOutOfHours)
	if err != nil {
		t.Fatalf("failed to query booking out_of_hours: %v", err)
	}
	if !dbOutOfHours {
		t.Error("expected out_of_hours=true in database")
	}
}

func TestAdminBookings_Create_OutOfHoursFalseByDefault(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)
	}

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

	_, err = tx.Exec(ctx, "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
	if err != nil {
		t.Fatalf("failed to set deposits_required: %v", err)
	}

	futureTime := clock.Now().Add(72 * time.Hour).Truncate(time.Second)
	req := bookings.AdminCreateBookingForUserRequest{
		UserID:     userID,
		StartTime:  futureTime,
		ServiceIDs: []string{serviceID},
	}

	handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler)
	w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req, ctx)

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

	var response map[string]interface{}
	if err := parseResponseBody(w, &response); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}

	bookingData, ok := response["booking"].(map[string]interface{})
	if !ok {
		t.Fatal("expected booking in response")
	}

	outOfHours, ok := bookingData["out_of_hours"].(bool)
	if !ok {
		t.Fatal("expected out_of_hours field in booking response")
	}
	if outOfHours {
		t.Error("expected out_of_hours=false (default) in create booking response")
	}
}

// TestAdminBookings_CountMatchesData verifies that the COUNT query returns the
// same total as the data query when filtering by date, even at BST boundary
// (23:xx UTC = 00:xx BST next day). Fix #1 — count query must use same London
// boundaries as the data query.
func TestAdminBookings_CountMatchesData(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)
	}
	serviceID, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	// Create a booking during BST period at 00:30 BST (= 23:30 UTC previous day)
	londonLoc, _ := time.LoadLocation("Europe/London")
	bkTime := time.Date(2099, 6, 15, 0, 30, 0, 0, londonLoc) // 00:30 BST = June 14 23:30 UTC
	_, err = fixtures.CreateTestBookingAtTime(tx, userID, serviceID, bkTime)
	if err != nil {
		t.Fatalf("failed to create booking at BST midnight: %v", err)
	}

	// Query with start_date = June 15 (the London date).
	// The COUNT query should return 1, matching the data query.
	handler := http.HandlerFunc(bookings.GetAllAdminBookingsHandler)
	endDate := time.Date(2099, 6, 15, 23, 59, 0, 0, time.UTC).Format("2006-01-02")
	startDate := time.Date(2099, 6, 15, 0, 0, 0, 0, time.UTC).Format("2006-01-02")

	w := makeAdminRequest(handler, "GET",
		"/api/admin/bookings?start_date="+startDate+"&end_date="+endDate, nil, ctx)

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

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

	if resp.Total != len(resp.Bookings) {
		t.Errorf("COUNT returned %d but data query returned %d bookings — COUNT must use same London boundaries as data query", resp.Total, len(resp.Bookings))
	}
	if resp.Total != 1 {
		t.Errorf("expected 1 booking (at BST boundary, London date June 15), got total=%d bookings=%d", resp.Total, len(resp.Bookings))
	}
}

// TestAdminBookings_CountMatchesData_AutumnDST verifies that the COUNT query
// returns the same total as the data query at the autumn DST boundary (BST→GMT
// transition on Oct 25, 2026). A booking at 00:30 BST on Oct 25 = 23:30 UTC
// Oct 24 — any date-boundary logic that uses UTC instead of London time would
// miss this booking or produce a COUNT/data mismatch.
func TestAdminBookings_CountMatchesData_AutumnDST(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)
	}
	serviceID, err := fixtures.CreateTestService(tx)
	if err != nil {
		t.Fatalf("failed to create test service: %v", err)
	}

	// Create a booking just after midnight BST on Oct 25, 2026 (autumn DST
	// transition day — BST ends at 02:00 BST → 01:00 GMT).
	// 00:30 BST on Oct 25 = 23:30 UTC on Oct 24.
	// This booking has a London date of Oct 25 but a UTC date of Oct 24,
	// so it would be missed by any query that uses UTC boundaries instead
	// of London timezone boundaries.
	londonLoc, _ := time.LoadLocation("Europe/London")
	bkTime := time.Date(2026, 10, 25, 0, 30, 0, 0, londonLoc) // 00:30 BST
	_, err = fixtures.CreateTestBookingAtTime(tx, userID, serviceID, bkTime)
	if err != nil {
		t.Fatalf("failed to create booking at autumn DST midnight: %v", err)
	}

	// Query with date range Oct 25 (the London date).
	// The booking should be found because its London date is Oct 25.
	handler := http.HandlerFunc(bookings.GetAllAdminBookingsHandler)
	startDate := "2026-10-25"
	endDate := "2026-10-25"

	w := makeAdminRequest(handler, "GET",
		"/api/admin/bookings?start_date="+startDate+"&end_date="+endDate, nil, ctx)

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

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

	if resp.Total != len(resp.Bookings) {
		t.Errorf("COUNT returned %d but data query returned %d bookings — COUNT must use same London boundaries as data query", resp.Total, len(resp.Bookings))
	}
	if resp.Total != 1 {
		t.Errorf("expected 1 booking (at autumn DST boundary, London date Oct 25), got total=%d bookings=%d", resp.Total, len(resp.Bookings))
	}
}
