<?php

namespace Tests\Feature;

use App\Enums\AppointmentStatus;
use App\Models\Appointment;
use App\Models\Business;
use App\Models\Customer;
use App\Models\Service;
use App\Models\StaffProfile;
use App\Models\User;
use App\Models\WorkingHour;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Tests\TestCase;

class PublicBookingTest extends TestCase
{
    use RefreshDatabase;

    protected function tearDown(): void
    {
        Carbon::setTestNow();

        parent::tearDown();
    }

    public function test_public_booking_pages_resolve_business_services_staff_and_slots(): void
    {
        Carbon::setTestNow('2026-01-04 08:00:00');
        [$business, $service, $staff] = $this->publicBookingSetup();

        $this->get('/book/'.$business->slug)
            ->assertOk()
            ->assertSee($business->name);

        $this->get('/book/'.$business->slug.'/services')
            ->assertOk()
            ->assertSee($service->name);

        $this->get('/book/'.$business->slug.'/staff?service_id='.$service->id)
            ->assertOk()
            ->assertSee($staff->name);

        $this->get('/book/'.$business->slug.'/date-time?service_id='.$service->id.'&staff_profile_id='.$staff->id.'&date=2026-01-05')
            ->assertOk()
            ->assertSee('10:00 AM');
    }

    public function test_customer_can_create_public_booking(): void
    {
        Carbon::setTestNow('2026-01-04 08:00:00');
        [$business, $service, $staff] = $this->publicBookingSetup();

        $this->post('/book/'.$business->slug.'/details', $this->detailsPayload($service, $staff))
            ->assertRedirect(route('public.booking.summary', $business->slug));

        $this->get('/book/'.$business->slug.'/summary')
            ->assertOk()
            ->assertSee('Jessica Public');

        $this->post('/book/'.$business->slug.'/summary')
            ->assertRedirect();

        $appointment = Appointment::query()->where('business_id', $business->id)->first();

        $this->assertNotNull($appointment);
        $this->assertDatabaseHas('customers', [
            'business_id' => $business->id,
            'name' => 'Jessica Public',
            'source' => 'Public booking page',
        ]);
        $this->assertSame(AppointmentStatus::Confirmed, $appointment->status);
        $this->assertNotNull($appointment->cancel_token);
        $this->assertNotNull($appointment->reschedule_token);
    }

    public function test_unavailable_public_slot_redirects_to_failed_state(): void
    {
        Carbon::setTestNow('2026-01-04 08:00:00');
        [$business, $service, $staff] = $this->publicBookingSetup();
        $customer = Customer::query()->create([
            'business_id' => $business->id,
            'name' => 'Existing Customer',
        ]);
        $this->appointment($business, $customer, $service, $staff, '2026-01-05 10:00:00');

        $this->post('/book/'.$business->slug.'/details', $this->detailsPayload($service, $staff, [
            'appointment_time' => '10:30',
        ]))->assertRedirect(route('public.booking.summary', $business->slug));

        $this->post('/book/'.$business->slug.'/summary')
            ->assertRedirect(route('public.booking.failed', $business->slug));

        $this->assertSame(1, Appointment::query()->forBusiness($business)->count());
        $this->get('/book/'.$business->slug.'/failed')
            ->assertOk()
            ->assertSee('Slot Unavailable');
    }

    public function test_public_reschedule_and_cancel_token_flow(): void
    {
        Carbon::setTestNow('2026-01-04 08:00:00');
        [$business, $service, $staff] = $this->publicBookingSetup();
        $customer = Customer::query()->create([
            'business_id' => $business->id,
            'name' => 'Token Customer',
        ]);
        $appointment = $this->appointment($business, $customer, $service, $staff, '2026-01-05 10:00:00');

        $this->get('/book/'.$business->slug.'/reschedule?token='.$appointment->reschedule_token)
            ->assertOk()
            ->assertSee('Reschedule Appointment');

        $this->post('/book/'.$business->slug.'/reschedule?token='.$appointment->reschedule_token, [
            'appointment_date' => '2026-01-05',
            'appointment_time' => '12:00',
        ])->assertRedirect();

        $appointment->refresh();
        $this->assertSame('12:00', $appointment->starts_at->format('H:i'));

        $this->get('/book/'.$business->slug.'/cancel?token='.$appointment->cancel_token)
            ->assertOk()
            ->assertSee('Cancel Appointment');

        $this->post('/book/'.$business->slug.'/cancel?token='.$appointment->cancel_token, [
            'cancel_reason' => 'Schedule conflict',
            'cancel_note' => 'Customer cannot attend.',
        ])->assertRedirect();

        $appointment->refresh();
        $this->assertSame(AppointmentStatus::Cancelled, $appointment->status);
    }

    private function publicBookingSetup(): array
    {
        $owner = User::factory()->create();
        $business = Business::factory()->create([
            'owner_id' => $owner->id,
            'name' => 'Public Test Salon',
            'slug' => 'public-test-salon',
            'status' => 'active',
            'phone' => '+1 555 111 2222',
            'address' => '12 Public Street',
        ]);
        $service = Service::query()->create([
            'business_id' => $business->id,
            'name' => 'Public Hair Cut',
            'slug' => 'public-hair-cut',
            'category' => 'Hair Services',
            'price' => 65,
            'duration_minutes' => 60,
            'buffer_before_minutes' => 10,
            'buffer_after_minutes' => 10,
            'status' => Service::STATUS_ACTIVE,
        ]);
        $staff = StaffProfile::query()->create([
            'business_id' => $business->id,
            'name' => 'Public Stylist',
            'role' => 'Stylist',
            'status' => StaffProfile::STATUS_ACTIVE,
            'availability_status' => StaffProfile::AVAILABILITY_AVAILABLE,
        ]);
        $staff->services()->attach($service->id, ['business_id' => $business->id]);
        WorkingHour::query()->create([
            'business_id' => $business->id,
            'staff_profile_id' => $staff->id,
            'day_of_week' => 1,
            'starts_at' => '09:00:00',
            'ends_at' => '17:00:00',
            'is_closed' => false,
        ]);

        return [$business, $service, $staff];
    }

    private function detailsPayload(Service $service, StaffProfile $staff, array $overrides = []): array
    {
        return array_merge([
            'service_id' => $service->id,
            'staff_profile_id' => $staff->id,
            'appointment_date' => '2026-01-05',
            'appointment_time' => '10:00',
            'customer_name' => 'Jessica Public',
            'customer_phone' => '+1 555 456 7890',
            'customer_email' => 'jessica.public@example.test',
            'notes' => 'Please use gentle products.',
        ], $overrides);
    }

    private function appointment(Business $business, Customer $customer, Service $service, StaffProfile $staff, string $startsAt): Appointment
    {
        $startsAt = Carbon::parse($startsAt);

        return Appointment::query()->create([
            'business_id' => $business->id,
            'customer_id' => $customer->id,
            'service_id' => $service->id,
            'staff_profile_id' => $staff->id,
            'booking_number' => 'BK-PUBLIC-'.random_int(1000, 9999),
            'status' => AppointmentStatus::Confirmed->value,
            'starts_at' => $startsAt,
            'ends_at' => $startsAt->copy()->addMinutes($service->duration_minutes),
            'duration_minutes' => $service->duration_minutes,
            'price' => $service->price,
            'payment_status' => 'unpaid',
            'cancel_token' => 'cancel-'.random_int(1000, 9999),
            'reschedule_token' => 'reschedule-'.random_int(1000, 9999),
        ]);
    }
}
