package bookings

import (
	"context"
	"errors"
	"testing"
	"time"

	"bookra/apps/backend/internal/db"
	"bookra/apps/backend/internal/domain"
)

func TestCreateAppointmentRejectsConflict(t *testing.T) {
	repo := db.NewMemoryRepository()
	service := NewService(repo, nil)

	availability, err := service.Availability(context.Background(), "studio-atelier")
	if err != nil {
		t.Fatalf("availability: %v", err)
	}

	var appointment domain.TimeSlot
	for _, slot := range availability.Slots {
		if slot.Mode == "appointment" {
			appointment = slot
			break
		}
	}
	if appointment.StartsAt == "" {
		t.Fatal("expected appointment slot")
	}

	first, err := service.Create(context.Background(), domain.CreateBookingRequest{
		TenantSlug:    "studio-atelier",
		BookingMode:   "appointment",
		ServiceID:     appointment.ServiceID,
		StaffID:       appointment.StaffID,
		LocationID:    appointment.LocationID,
		CustomerName:  "First",
		CustomerEmail: "first@example.com",
		StartsAt:      appointment.StartsAt,
		EndsAt:        appointment.EndsAt,
	})
	if err != nil {
		t.Fatalf("first create: %v", err)
	}
	if first.Status != "confirmed" {
		t.Fatalf("expected confirmed, got %s", first.Status)
	}

	_, err = service.Create(context.Background(), domain.CreateBookingRequest{
		TenantSlug:    "studio-atelier",
		BookingMode:   "appointment",
		ServiceID:     appointment.ServiceID,
		StaffID:       appointment.StaffID,
		LocationID:    appointment.LocationID,
		CustomerName:  "Second",
		CustomerEmail: "second@example.com",
		StartsAt:      appointment.StartsAt,
		EndsAt:        appointment.EndsAt,
	})
	if err == nil {
		t.Fatal("expected conflict error")
	}
	if err != ErrBookingConflict {
		t.Fatalf("expected ErrBookingConflict, got %v", err)
	}
}

func TestCreateClassFallsBackToWaitlistWhenCapacityReached(t *testing.T) {
	repo := db.NewMemoryRepository()
	service := NewService(repo, nil)

	availability, err := service.Availability(context.Background(), "studio-atelier")
	if err != nil {
		t.Fatalf("availability: %v", err)
	}

	var classSlot domain.TimeSlot
	for _, slot := range availability.Slots {
		if slot.Mode == "class" {
			classSlot = slot
			break
		}
	}
	if classSlot.ClassSessionID == nil {
		t.Fatal("expected class slot")
	}

	for i := 0; i < 4; i++ {
		response, err := service.Create(context.Background(), domain.CreateBookingRequest{
			TenantSlug:     "studio-atelier",
			BookingMode:    "class",
			ClassSessionID: classSlot.ClassSessionID,
			LocationID:     classSlot.LocationID,
			CustomerName:   "Capacity",
			CustomerEmail:  "capacity@example.com",
			StartsAt:       classSlot.StartsAt,
			EndsAt:         classSlot.EndsAt,
		})
		if err != nil {
			t.Fatalf("create within capacity: %v", err)
		}
		if response.Status != "confirmed" {
			t.Fatalf("expected confirmed within capacity, got %s", response.Status)
		}
	}

	response, err := service.Create(context.Background(), domain.CreateBookingRequest{
		TenantSlug:     "studio-atelier",
		BookingMode:    "class",
		ClassSessionID: classSlot.ClassSessionID,
		LocationID:     classSlot.LocationID,
		CustomerName:   "Waitlist",
		CustomerEmail:  "waitlist@example.com",
		StartsAt:       classSlot.StartsAt,
		EndsAt:         classSlot.EndsAt,
	})
	if err != nil {
		t.Fatalf("create waitlist: %v", err)
	}
	if response.Status != "waitlisted" {
		t.Fatalf("expected waitlisted, got %s", response.Status)
	}
}

func TestCreateAppointmentRequiresTenantService(t *testing.T) {
	repo := db.NewMemoryRepository()
	service := NewService(repo, nil)

	_, err := service.Create(context.Background(), domain.CreateBookingRequest{
		TenantSlug:    "studio-atelier",
		BookingMode:   "appointment",
		CustomerName:  "Missing Service",
		CustomerEmail: "missing@example.com",
		StartsAt:      time.Now().UTC().Add(24 * time.Hour).Format(time.RFC3339),
		EndsAt:        time.Now().UTC().Add(25 * time.Hour).Format(time.RFC3339),
	})
	if !errors.Is(err, ErrInvalidBooking) {
		t.Fatalf("expected ErrInvalidBooking, got %v", err)
	}
}

func TestCreateClassRequiresExistingSession(t *testing.T) {
	repo := db.NewMemoryRepository()
	service := NewService(repo, nil)
	missingSessionID := "11111111-1111-1111-1111-111111111111"

	_, err := service.Create(context.Background(), domain.CreateBookingRequest{
		TenantSlug:     "studio-atelier",
		BookingMode:    "class",
		ClassSessionID: &missingSessionID,
		CustomerName:   "Missing Session",
		CustomerEmail:  "missing@example.com",
		StartsAt:       time.Now().UTC().Add(48 * time.Hour).Format(time.RFC3339),
		EndsAt:         time.Now().UTC().Add(49 * time.Hour).Format(time.RFC3339),
	})
	if !errors.Is(err, ErrInvalidBooking) {
		t.Fatalf("expected ErrInvalidBooking, got %v", err)
	}
}

func TestAvailabilityGeneratesUpcomingSlots(t *testing.T) {
	repo := db.NewMemoryRepository()
	service := NewService(repo, nil)

	availability, err := service.Availability(context.Background(), "studio-atelier")
	if err != nil {
		t.Fatalf("availability: %v", err)
	}
	if len(availability.Slots) == 0 {
		t.Fatal("expected slots")
	}

	for _, slot := range availability.Slots {
		startsAt, err := time.Parse(time.RFC3339, slot.StartsAt)
		if err != nil {
			t.Fatalf("parse startsAt: %v", err)
		}
		if startsAt.Before(time.Now().UTC().Add(90 * time.Minute)) {
			t.Fatalf("expected upcoming slot, got %s", slot.StartsAt)
		}
	}
}

func TestCreateSchedulesReminderJobForUpcomingAppointment(t *testing.T) {
	repo := db.NewMemoryRepository()
	service := NewService(repo, nil)

	availability, err := service.Availability(context.Background(), "studio-atelier")
	if err != nil {
		t.Fatalf("availability: %v", err)
	}

	var appointment domain.TimeSlot
	for _, slot := range availability.Slots {
		if slot.Mode == "appointment" {
			appointment = slot
			break
		}
	}
	if appointment.StartsAt == "" {
		t.Fatal("expected appointment slot")
	}

	_, err = service.Create(context.Background(), domain.CreateBookingRequest{
		TenantSlug:    "studio-atelier",
		BookingMode:   "appointment",
		ServiceID:     appointment.ServiceID,
		StaffID:       appointment.StaffID,
		LocationID:    appointment.LocationID,
		CustomerName:  "Reminder",
		CustomerEmail: "reminder@example.com",
		StartsAt:      time.Now().UTC().Add(30 * time.Hour).Format(time.RFC3339),
		EndsAt:        time.Now().UTC().Add(31 * time.Hour).Format(time.RFC3339),
	})
	if err != nil {
		t.Fatalf("create: %v", err)
	}

	reminders, err := repo.ListDueReminderJobs(context.Background(), time.Now().UTC().Add(365*24*time.Hour), 10)
	if err != nil {
		t.Fatalf("list reminder jobs: %v", err)
	}
	if len(reminders) == 0 {
		t.Fatal("expected reminder job to be scheduled")
	}
}
