package main

import (
	"errors"
	"fmt"
	"testing"
)

// TestClassifyBookingError_GroundedTaxonomy locks in the mapping against the
// REAL Cal.com error bodies captured by probing the live /book endpoint
// (2026-06-21). Each case feeds a *CalcomAPIError built from an actual response
// and asserts the curated visitor copy + retry affordance. The raw Cal.com
// message must never be the rendered string.
func TestClassifyBookingError_GroundedTaxonomy(t *testing.T) {
	const adminMsg = "Call us on 1234."

	cases := []struct {
		name     string
		err      error
		wantMsg  string
		wantRtry bool
	}{
		{
			// Live: status 400, BadRequestException: "Attempting to book a meeting in the past."
			name:     "past instant",
			err:      &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BadRequestException", Message: "Attempting to book a meeting in the past."},
			wantMsg:  msgSlotUnavailable,
			wantRtry: true,
		},
		{
			// Live: status 400, BadRequestException: "User either already has booking at this time or is not available"
			name:     "slot taken / host unavailable",
			err:      &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BadRequestException", Message: "User either already has booking at this time or is not available"},
			wantMsg:  msgSlotUnavailable,
			wantRtry: true,
		},
		{
			// Live: status 400, BAD_REQUEST: "responses - {guests}email_validation_error, "
			name:     "invalid guest email",
			err:      &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BAD_REQUEST", Message: "responses - {guests}email_validation_error, "},
			wantMsg:  msgInvalidGuestEmail,
			wantRtry: false,
		},
		{
			// Cal.com phone rejection: "{attendeePhoneNumber} invalid_number".
			name:     "invalid phone number",
			err:      &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BAD_REQUEST", Message: "responses - {attendeePhoneNumber}invalid_number"},
			wantMsg:  msgInvalidPhone,
			wantRtry: false,
		},
		{
			// A rejected custom-field answer (a required field / generic responses error
			// that names no guest/phone signal).
			name:     "rejected custom field answer",
			err:      &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BAD_REQUEST", Message: "responses - {favourite-colour} is required"},
			wantMsg:  msgRejectedAnswer,
			wantRtry: false,
		},
		{
			name:     "event fully booked / booking limit",
			err:      &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BadRequestException", Message: "booking_limit reached for this event type"},
			wantMsg:  msgEventFullyBooked,
			wantRtry: true,
		},
		{
			// Live: status 404, NotFoundException — unmapped → Tier-2 admin message.
			name:     "not found (unmapped 4xx) → admin message",
			err:      &CalcomAPIError{Endpoint: "booking", Status: 404, Code: "NotFoundException", Message: "Event type with slug nope not found"},
			wantMsg:  adminMsg,
			wantRtry: false,
		},
		{
			name:     "auth failure (401) → admin message",
			err:      &CalcomAPIError{Endpoint: "booking", Status: 401, Code: "UnauthorizedException", Message: "Invalid API Key"},
			wantMsg:  adminMsg,
			wantRtry: false,
		},
		{
			name:     "cal.com 5xx → admin message",
			err:      &CalcomAPIError{Endpoint: "booking", Status: 503, Code: "", Message: "service unavailable"},
			wantMsg:  adminMsg,
			wantRtry: false,
		},
		{
			name:     "network/transport error (not a CalcomAPIError) → admin message",
			err:      fmt.Errorf("post bookings: %w", errors.New("dial tcp: i/o timeout")),
			wantMsg:  adminMsg,
			wantRtry: false,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := classifyBookingError(tc.err, adminMsg)
			if got.message != tc.wantMsg {
				t.Errorf("message = %q, want %q", got.message, tc.wantMsg)
			}
			if got.canRetry != tc.wantRtry {
				t.Errorf("canRetry = %v, want %v", got.canRetry, tc.wantRtry)
			}
			// The raw Cal.com message must never be the rendered copy.
			var apiErr *CalcomAPIError
			if errors.As(tc.err, &apiErr) && apiErr.Message != "" && got.message == apiErr.Message {
				t.Errorf("rendered the raw Cal.com message verbatim: %q", got.message)
			}
		})
	}
}

// TestClassifyBookingError_BlankAdminMessageIsCallerDefaulted documents that the
// classifier renders whatever adminMessage it is given for Tier-2 — the caller
// (HandleCreateBooking) substitutes DefaultUnavailableMessage when the form
// field is blank, so the two stay in sync via the single const.
func TestClassifyBookingError_BlankAdminMessageIsCallerDefaulted(t *testing.T) {
	err := &CalcomAPIError{Endpoint: "booking", Status: 404, Message: "nope"}
	got := classifyBookingError(err, DefaultUnavailableMessage)
	if got.message != DefaultUnavailableMessage {
		t.Errorf("message = %q, want DefaultUnavailableMessage", got.message)
	}
}
