<?php

namespace Tests\Feature;

use App\Http\Middleware\EnsurePlatformSession;
use App\Models\Member;
use App\Models\Organization;
use App\Models\Recording;
use App\Models\Room;
use App\Models\Session;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;

class MeetWebTest extends TestCase
{
    use RefreshDatabase;

    protected User $user;

    protected Organization $organization;

    protected function setUp(): void
    {
        parent::setUp();
        $this->withoutMiddleware(EnsurePlatformSession::class);

        $this->user = User::create([
            'public_id' => 'test-user-001',
            'name' => 'Test User',
            'email' => 'test@example.com',
        ]);

        $this->organization = Organization::create([
            'owner_ref' => $this->user->public_id,
            'name' => 'Test Org',
            'slug' => 'test-org',
            'timezone' => 'UTC',
            'settings' => ['onboarded' => true],
        ]);

        Member::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'user_ref' => $this->user->public_id,
            'role' => 'owner',
        ]);

        $base = rtrim((string) config('billing.api_url'), '/');
        Http::fake([
            $base.'/can-afford*' => Http::response(['affordable' => true]),
            $base.'/debit' => Http::response(['ok' => true]),
            $base.'/balance*' => Http::response(['balance_minor' => 100000]),
        ]);
    }

    public function test_health_endpoint(): void
    {
        $this->getJson('/api/health')
            ->assertOk()
            ->assertJson(['status' => 'ok', 'app' => 'meet']);
    }

    public function test_guest_redirected_from_dashboard(): void
    {
        $this->get('/dashboard')->assertRedirect();
    }

    public function test_guest_root_uses_silent_sso_not_interactive_auth(): void
    {
        $response = $this->get('/');

        $response->assertRedirect(route('sso.connect'));
        $this->assertStringNotContainsString('interactive=1', (string) $response->headers->get('Location'));
    }

    public function test_sso_callback_sends_guest_without_session_to_marketing_page(): void
    {
        $this->get('/sso/callback?error=login_required')
            ->assertRedirect(config('ladill.marketing_url'));
    }

    public function test_onboarded_user_can_view_dashboard(): void
    {
        $this->actingAs($this->user)
            ->get('/dashboard')
            ->assertOk();
    }

    public function test_user_can_create_instant_meeting(): void
    {
        $this->actingAs($this->user)
            ->get('/meetings/instant')
            ->assertRedirect();

        $this->assertDatabaseHas('meet_rooms', [
            'host_user_ref' => $this->user->public_id,
            'type' => 'instant',
        ]);
    }

    public function test_instant_meeting_opens_room(): void
    {
        $response = $this->actingAs($this->user)
            ->get('/meetings/instant');

        $response->assertRedirect();

        $session = Session::where('owner_ref', $this->user->public_id)->latest()->first();
        $this->assertNotNull($session);
        $this->assertTrue($session->isLive());

        $this->actingAs($this->user)
            ->get('/room/'.$session->uuid)
            ->assertOk()
            ->assertSee($session->room->title);
    }

    public function test_host_can_start_scheduled_meeting_into_room(): void
    {
        $room = $this->createRoom();

        $this->actingAs($this->user)
            ->post('/meetings/'.$room->uuid.'/start')
            ->assertRedirect();

        $session = $room->fresh()->activeSession();
        $this->assertNotNull($session);

        $this->actingAs($this->user)
            ->get('/room/'.$session->uuid)
            ->assertOk();
    }

    public function test_guest_cannot_join_before_host_starts(): void
    {
        $room = $this->createRoom([
            'settings' => array_merge(config('meet.default_settings'), [
                'join_before_host' => false,
            ]),
        ]);

        $this->post('/r/'.$room->uuid.'/enter', [
            'display_name' => 'Guest User',
        ])
            ->assertRedirect()
            ->assertSessionHasErrors('join');
    }

    public function test_guest_can_join_live_meeting_and_enter_room(): void
    {
        $room = $this->createRoom([
            'settings' => array_merge(config('meet.default_settings'), [
                'join_before_host' => true,
                'waiting_room' => false,
            ]),
        ]);
        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'room-'.$room->uuid,
            'status' => 'live',
            'started_at' => now(),
        ]);
        $room->update(['status' => 'live']);

        $this->post('/r/'.$room->uuid.'/enter', [
            'display_name' => 'Guest User',
        ])
            ->assertRedirect(route('meet.room', $session));

        $this->get('/room/'.$session->uuid)
            ->assertOk()
            ->assertSee('data-display-name="Guest User"', false);
    }

    public function test_anonymous_guest_does_not_take_over_host_participant(): void
    {
        $room = $this->createRoom([
            'settings' => array_merge(config('meet.default_settings'), [
                'join_before_host' => true,
                'waiting_room' => false,
            ]),
        ]);
        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'room-'.$room->uuid,
            'status' => 'live',
            'started_at' => now(),
        ]);
        $room->update(['status' => 'live']);

        $hostParticipant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->post('/r/'.$room->uuid.'/enter', [
            'display_name' => 'Guest User',
        ])->assertRedirect(route('meet.room', $session));

        $hostParticipant->refresh();
        $this->assertSame('host', $hostParticipant->role);
        $this->assertSame($this->user->name, $hostParticipant->display_name);

        $guest = \App\Models\Participant::query()
            ->where('session_id', $session->id)
            ->where('display_name', 'Guest User')
            ->first();

        $this->assertNotNull($guest);
        $this->assertSame('guest', $guest->role);
        $this->assertNotSame($hostParticipant->uuid, $guest->uuid);
    }

    public function test_guest_is_placed_in_waiting_room_when_enabled(): void
    {
        $room = $this->createRoom([
            'settings' => array_merge(config('meet.default_settings'), [
                'join_before_host' => true,
                'waiting_room' => true,
            ]),
        ]);
        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'room-'.$room->uuid,
            'status' => 'live',
            'started_at' => now(),
        ]);
        $room->update(['status' => 'live']);

        $this->post('/r/'.$room->uuid.'/enter', [
            'display_name' => 'Waiting Guest',
        ])
            ->assertRedirect(route('meet.join.waiting', $room));

        $this->assertDatabaseHas('meet_participants', [
            'session_id' => $session->id,
            'display_name' => 'Waiting Guest',
            'status' => 'waiting',
        ]);
    }

    public function test_leave_redirects_to_feedback_page(): void
    {
        $room = $this->createRoom(['settings' => array_merge(config('meet.default_settings'), ['waiting_room' => false])]);
        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'room-'.$room->uuid,
            'status' => 'live',
            'started_at' => now(),
        ]);
        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'display_name' => 'Guest',
            'role' => 'guest',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
            ->post('/room/'.$session->uuid.'/leave')
            ->assertRedirect(route('meet.left', $session));
    }

    public function test_participant_redirected_to_ended_page_when_meeting_ended(): void
    {
        $room = $this->createRoom(['settings' => array_merge(config('meet.default_settings'), ['waiting_room' => false])]);
        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'room-'.$room->uuid,
            'status' => 'ended',
            'started_at' => now()->subHour(),
            'ended_at' => now(),
        ]);
        $room->update(['status' => 'ended']);
        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'display_name' => 'Guest',
            'role' => 'guest',
            'status' => 'left',
            'joined_at' => now()->subHour(),
            'left_at' => now(),
        ]);

        $this->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
            ->get('/room/'.$session->uuid)
            ->assertRedirect(route('meet.ended', $session));
    }

    public function test_poll_returns_ended_payload_when_meeting_ended(): void
    {
        $room = $this->createRoom();
        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'room-'.$room->uuid,
            'status' => 'ended',
            'started_at' => now()->subHour(),
            'ended_at' => now(),
        ]);
        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'display_name' => 'Guest',
            'role' => 'guest',
            'status' => 'left',
            'joined_at' => now()->subHour(),
            'left_at' => now(),
        ]);

        $this->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
            ->getJson('/room/'.$session->uuid.'/poll')
            ->assertOk()
            ->assertJson([
                'ended' => true,
                'redirect' => route('meet.ended', $session),
            ]);
    }

    public function test_waiting_guest_redirected_when_host_ends_meeting(): void
    {
        $room = $this->createRoom(['settings' => array_merge(config('meet.default_settings'), ['waiting_room' => true])]);
        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'room-'.$room->uuid,
            'status' => 'ended',
            'started_at' => now()->subHour(),
            'ended_at' => now(),
        ]);
        $room->update(['status' => 'ended']);
        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'display_name' => 'Waiting Guest',
            'role' => 'guest',
            'status' => 'left',
            'joined_at' => now()->subHour(),
            'left_at' => now(),
        ]);

        $this->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
            ->getJson('/r/'.$room->uuid.'/waiting/status')
            ->assertOk()
            ->assertJson([
                'ended' => true,
                'redirect' => route('meet.ended', $session),
            ]);
    }

    public function test_authenticated_user_can_join_live_meeting_without_display_name(): void
    {
        $room = $this->createRoom();
        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'room-'.$room->uuid,
            'status' => 'live',
            'started_at' => now(),
        ]);
        $room->update(['status' => 'live']);

        $this->actingAs($this->user)
            ->post('/r/'.$room->uuid.'/enter')
            ->assertRedirect(route('meet.room', $session));

        $this->actingAs($this->user)
            ->get('/room/'.$session->uuid)
            ->assertOk();
    }

    public function test_room_has_join_url(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Test Meeting',
            'type' => 'instant',
            'status' => 'scheduled',
            'scheduled_at' => now(),
            'timezone' => 'UTC',
            'settings' => config('meet.default_settings'),
        ]);

        $this->assertStringContainsString('meet.ladill.com/r/'.$room->uuid, $room->joinUrl());
    }

    public function test_user_can_view_recordings_index(): void
    {
        $room = $this->createRoom();
        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'room-'.$room->uuid,
            'status' => 'ended',
            'started_at' => now()->subHour(),
            'ended_at' => now(),
        ]);
        Recording::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'status' => 'ready',
            'storage_path' => 'recordings/test.mp4',
        ]);

        $this->actingAs($this->user)
            ->get('/recordings')
            ->assertOk()
            ->assertSee('Recordings');
    }

    public function test_admin_can_update_security_policy(): void
    {
        $this->actingAs($this->user)
            ->put('/settings/security', [
                'org_only' => '1',
                'authenticated_only' => '1',
                'allowed_domains' => 'example.com, ladill.com',
                'recording_host_only' => '1',
                'session_idle_minutes' => 90,
            ])
            ->assertRedirect(route('meet.settings.security'));

        $this->organization->refresh();
        $this->assertTrue($this->organization->securitySetting('org_only'));
        $this->assertContains('example.com', $this->organization->securitySetting('allowed_domains'));
    }

    public function test_user_can_access_personal_room(): void
    {
        $this->actingAs($this->user)
            ->get('/meetings/personal')
            ->assertRedirect();

        $this->assertDatabaseHas('meet_rooms', [
            'host_user_ref' => $this->user->public_id,
            'type' => 'personal',
        ]);
    }

    public function test_admin_can_view_reports(): void
    {
        $this->actingAs($this->user)
            ->get('/reports')
            ->assertOk()
            ->assertSee('Reports');
    }

    public function test_service_api_can_create_room(): void
    {
        \Illuminate\Support\Facades\Http::fake();
        config(['meet.service_api_keys.care' => 'test-care-key']);

        $this->postJson('/api/service/v1/rooms', [
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Care consultation',
            'source' => ['app' => 'care', 'entity_type' => 'appointment', 'entity_id' => 'appt-123'],
        ], ['Authorization' => 'Bearer test-care-key'])
            ->assertCreated()
            ->assertJsonPath('room.title', 'Care consultation');

        $this->assertDatabaseHas('meet_rooms', [
            'title' => 'Care consultation',
            'host_user_ref' => $this->user->public_id,
        ]);
    }

    public function test_service_api_resolves_organization_from_owner_ref(): void
    {
        \Illuminate\Support\Facades\Http::fake();
        config(['meet.service_api_keys.crm' => 'test-crm-key']);

        $this->postJson('/api/service/v1/rooms', [
            'owner_ref' => $this->user->public_id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'CRM follow-up',
            'scheduled_at' => now()->addDay()->toIso8601String(),
            'source' => ['app' => 'crm', 'entity_type' => 'deal', 'entity_id' => '42'],
        ], ['Authorization' => 'Bearer test-crm-key'])
            ->assertCreated()
            ->assertJsonPath('room.organization_id', $this->organization->id);
    }

    public function test_service_api_can_create_room_for_invoice_app(): void
    {
        \Illuminate\Support\Facades\Http::fake();
        config(['meet.service_api_keys.invoice' => 'test-invoice-key']);

        $this->postJson('/api/service/v1/rooms', [
            'owner_ref' => $this->user->public_id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Invoice review',
            'scheduled_at' => now()->addDay()->toIso8601String(),
            'source' => ['app' => 'invoice', 'entity_type' => 'invoice', 'entity_id' => '99'],
        ], ['Authorization' => 'Bearer test-invoice-key'])
            ->assertCreated()
            ->assertJsonPath('room.title', 'Invoice review');
    }

    public function test_service_api_can_create_town_hall_room(): void
    {
        \Illuminate\Support\Facades\Http::fake();
        config(['meet.service_api_keys.events' => 'test-events-key']);

        $this->postJson('/api/service/v1/rooms', [
            'owner_ref' => $this->user->public_id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Opening keynote',
            'type' => 'town_hall',
            'scheduled_at' => now()->addDay()->toIso8601String(),
            'duration_minutes' => 90,
            'source' => ['app' => 'events', 'entity_type' => 'virtual_session', 'entity_id' => 'vs-1'],
        ], ['Authorization' => 'Bearer test-events-key'])
            ->assertCreated()
            ->assertJsonPath('room.type', 'town_hall');

        $this->assertDatabaseHas('meet_rooms', ['type' => 'town_hall', 'title' => 'Opening keynote']);
    }

    public function test_service_api_upserts_room_by_source_entity_id(): void
    {
        \Illuminate\Support\Facades\Http::fake();
        config(['meet.service_api_keys.events' => 'test-events-key']);

        $payload = [
            'owner_ref' => $this->user->public_id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Session A',
            'type' => 'webinar',
            'scheduled_at' => now()->addDay()->toIso8601String(),
            'source' => ['app' => 'events', 'entity_type' => 'virtual_session', 'entity_id' => 'vs-dup'],
        ];

        $this->postJson('/api/service/v1/rooms', $payload, ['Authorization' => 'Bearer test-events-key'])
            ->assertCreated();

        $payload['title'] = 'Session A (updated)';

        $this->postJson('/api/service/v1/rooms', $payload, ['Authorization' => 'Bearer test-events-key'])
            ->assertOk()
            ->assertJsonPath('room.title', 'Session A (updated)');

        $this->assertSame(1, \App\Models\Room::where('source->entity_id', 'vs-dup')->count());
    }

    public function test_service_api_lookup_by_source(): void
    {
        \Illuminate\Support\Facades\Http::fake();
        config(['meet.service_api_keys.events' => 'test-events-key']);

        $this->postJson('/api/service/v1/rooms', [
            'owner_ref' => $this->user->public_id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Lookup test',
            'type' => 'town_hall',
            'scheduled_at' => now()->addDay()->toIso8601String(),
            'source' => ['app' => 'events', 'entity_type' => 'virtual_session', 'entity_id' => 'vs-lookup'],
        ], ['Authorization' => 'Bearer test-events-key'])->assertCreated();

        $this->getJson('/api/service/v1/rooms/by-source?source_app=events&entity_id=vs-lookup&owner_ref='.$this->user->public_id, [
            'Authorization' => 'Bearer test-events-key',
        ])
            ->assertOk()
            ->assertJsonPath('room.title', 'Lookup test');
    }

    public function test_scheduled_room_syncs_to_ladill_mail_calendar(): void
    {
        \Illuminate\Support\Facades\Http::fake([
            'mail.ladill.com/*' => \Illuminate\Support\Facades\Http::response(['data' => ['id' => 99]], 201),
        ]);

        config([
            'meet.calendar.mail.api_url' => 'https://mail.ladill.com/api/service/v1',
            'meet.calendar.mail.api_key' => 'mail-test-key',
        ]);

        $this->actingAs($this->user)
            ->post('/meetings', [
                'title' => 'Calendar sync test',
                'scheduled_at' => now()->addDay()->format('Y-m-d H:i'),
                'duration_minutes' => 30,
            ])
            ->assertRedirect();

        $room = \App\Models\Room::where('title', 'Calendar sync test')->first();
        $this->assertNotNull($room);
        $this->assertSame('99', (string) $room->calendar_event_id);

        \Illuminate\Support\Facades\Http::assertSent(function ($request) {
            return str_contains($request->url(), '/calendar/events')
                && $request['mailbox_email'] === strtolower($this->user->email);
        });
    }

    public function test_invitation_rsvp_accepts(): void
    {
        $room = $this->createRoom();
        $invitation = \App\Models\Invitation::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'email' => 'guest@example.com',
            'status' => 'pending',
            'rsvp_token' => 'test-rsvp-token',
        ]);

        $this->post('/invite/'.$invitation->uuid.'/rsvp?token=test-rsvp-token', [
            'status' => 'accepted',
        ])->assertOk();

        $this->assertDatabaseHas('meet_invitations', [
            'id' => $invitation->id,
            'status' => 'accepted',
        ]);
    }

    public function test_admin_can_add_webhook_endpoint(): void
    {
        $this->actingAs($this->user)
            ->post('/settings/webhooks', [
                'url' => 'https://care.example.com/webhooks/meet',
                'secret' => 'whsec_test',
                'events' => ['meeting.started', 'meeting.ended'],
            ])
            ->assertRedirect(route('meet.settings.webhooks'));

        $this->assertDatabaseHas('meet_webhook_endpoints', [
            'organization_id' => $this->organization->id,
            'url' => 'https://care.example.com/webhooks/meet',
        ]);
    }

    public function test_webinar_registration_redirects_without_events_link(): void
    {
        $room = $this->createRoom(['type' => 'webinar']);

        $this->post('/w/'.$room->uuid.'/register', [
            'email' => 'attendee@example.com',
            'display_name' => 'Attendee One',
        ])->assertRedirect(route('meet.webinars.show', $room));
    }

    public function test_host_can_create_webinar_room(): void
    {
        $this->actingAs($this->user)
            ->post('/webinars', [
                'title' => 'Product launch',
                'scheduled_at' => now()->addDay()->format('Y-m-d\TH:i'),
                'duration_minutes' => 60,
            ])
            ->assertRedirect();

        $this->assertDatabaseHas('meet_rooms', [
            'title' => 'Product launch',
            'type' => 'webinar',
        ]);
    }

    public function test_host_can_enable_panel_discussions_on_webinar(): void
    {
        $this->actingAs($this->user)
            ->post('/webinars', [
                'title' => 'Panel webinar',
                'scheduled_at' => now()->addDay()->format('Y-m-d\TH:i'),
                'duration_minutes' => 60,
                'panel_discussions' => '1',
            ])
            ->assertRedirect();

        $room = Room::where('title', 'Panel webinar')->firstOrFail();
        $this->assertTrue($room->setting('panel_discussions'));
        $this->assertSame('plenary', $room->setting('stage_layout'));

        $this->actingAs($this->user)
            ->post(route('meet.webinars.settings.update', $room), [
                'panel_discussions' => '0',
            ])
            ->assertRedirect(route('meet.webinars.show', $room));

        $this->assertFalse($room->fresh()->setting('panel_discussions'));
    }

    public function test_user_can_create_channel_and_post_message(): void
    {
        $this->actingAs($this->user)
            ->post('/channels', [
                'name' => 'General',
                'visibility' => 'public',
            ])
            ->assertRedirect();

        $channel = \App\Models\Channel::first();
        $this->assertNotNull($channel);

        $this->actingAs($this->user)
            ->post('/channels/'.$channel->uuid.'/messages', ['body' => 'Hello team'])
            ->assertRedirect();

        $this->assertDatabaseHas('meet_channel_messages', [
            'channel_id' => $channel->id,
            'body' => 'Hello team',
        ]);
    }

    public function test_admin_can_view_usage_dashboard(): void
    {
        $this->actingAs($this->user)
            ->get('/admin/usage')
            ->assertOk()
            ->assertSee('Usage');
    }

    public function test_mail_calendar_can_be_connected(): void
    {
        config(['meet.calendar.mail.api_key' => 'test-mail-key']);

        $this->actingAs($this->user)
            ->post('/settings/calendar/mail')
            ->assertRedirect(route('meet.settings.calendar'));

        $this->assertDatabaseHas('meet_calendar_connections', [
            'user_ref' => $this->user->public_id,
            'provider' => 'mail',
        ]);
    }

    public function test_host_can_create_conference(): void
    {
        $this->actingAs($this->user)
            ->post('/conferences', [
                'title' => 'All hands',
                'scheduled_at' => now()->addDay()->format('Y-m-d\TH:i'),
                'duration_minutes' => 90,
            ])
            ->assertRedirect();

        $this->assertDatabaseHas('meet_rooms', [
            'title' => 'All hands',
            'type' => 'town_hall',
        ]);
    }

    public function test_green_room_register_presenter_keeps_host_role(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'NextGen Summit',
            'type' => 'town_hall',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => config('meet.default_settings'),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid.'-green',
            'status' => 'live',
            'session_mode' => 'green_room',
            'started_at' => now(),
        ]);

        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        app(\App\Services\Meet\ConferenceService::class)->registerPresenter($session, $this->user, 'host');

        $this->assertSame('host', $participant->fresh()->role);
    }

    public function test_host_go_live_auto_joins_live_session(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'NextGen Summit',
            'type' => 'town_hall',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'green_room' => true,
                'stage_mode' => true,
            ]),
        ]);

        $greenSession = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid.'-green',
            'status' => 'live',
            'session_mode' => 'green_room',
            'presenter_refs' => [$this->user->public_id],
            'started_at' => now(),
        ]);

        $hostParticipant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $greenSession->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$greenSession->uuid}" => $hostParticipant->uuid])
            ->postJson(route('meet.room.townhall.live', $greenSession))
            ->assertOk()
            ->assertJson(['mode' => 'live']);

        $liveSession = Session::query()
            ->where('room_id', $room->id)
            ->where('session_mode', 'live')
            ->first();

        $this->assertNotNull($liveSession);
        $this->assertSame('ended', $greenSession->fresh()->status);

        $this->actingAs($this->user)
            ->get(route('meet.room', $liveSession))
            ->assertOk();

        $this->assertDatabaseHas('meet_participants', [
            'session_id' => $liveSession->id,
            'user_ref' => $this->user->public_id,
            'role' => 'host',
            'status' => 'joined',
        ]);
    }

    public function test_green_room_poll_redirects_presenters_to_live_session(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Panel prep',
            'type' => 'town_hall',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => config('meet.default_settings'),
        ]);

        $greenSession = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid.'-green',
            'status' => 'live',
            'session_mode' => 'green_room',
            'presenter_refs' => [$this->user->public_id],
            'started_at' => now(),
        ]);

        $hostParticipant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $greenSession->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        app(\App\Services\Meet\TownHallService::class)->goLive($greenSession, $this->user);

        $liveSession = Session::query()
            ->where('room_id', $room->id)
            ->where('session_mode', 'live')
            ->firstOrFail();

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$greenSession->uuid}" => $hostParticipant->uuid])
            ->getJson(route('meet.room.poll', $greenSession))
            ->assertOk()
            ->assertJson([
                'ended' => true,
                'transition' => 'go_live',
                'redirect' => route('meet.room', $liveSession),
            ]);
    }

    public function test_host_can_end_conference_and_view_details(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'NextGen Summit',
            'type' => 'town_hall',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'green_room' => true,
                'stage_mode' => true,
                'presenter_refs' => [],
            ]),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'live',
            'started_at' => now(),
        ]);

        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
            ->post('/room/'.$session->uuid.'/end')
            ->assertRedirect(route('meet.conferences.show', $room))
            ->assertSessionHas('success', 'Conference ended.');

        $this->actingAs($this->user)
            ->get(route('meet.conferences.show', $room))
            ->assertOk()
            ->assertSee('NextGen Summit');
    }

    public function test_host_can_add_speakers_after_conference_created(): void
    {
        $speaker = User::create([
            'public_id' => 'speaker-user-001',
            'name' => 'Keynote Speaker',
            'email' => 'speaker@example.com',
        ]);

        Member::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'user_ref' => $speaker->public_id,
            'role' => 'member',
        ]);

        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Product launch',
            'type' => 'town_hall',
            'status' => 'scheduled',
            'scheduled_at' => now()->addDay(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'green_room' => true,
                'stage_mode' => true,
                'presenter_refs' => [],
            ]),
        ]);

        $this->actingAs($this->user)
            ->post(route('meet.conferences.speakers.update', $room), [
                'presenter_refs' => [$speaker->public_id],
                'invite_emails' => 'guest-speaker@example.com',
            ])
            ->assertRedirect(route('meet.conferences.show', $room))
            ->assertSessionHas('success');

        $room->refresh();
        $this->assertContains($speaker->public_id, $room->setting('presenter_refs'));

        $this->assertDatabaseHas('meet_invitations', [
            'room_id' => $room->id,
            'email' => 'guest-speaker@example.com',
            'role' => 'panelist',
        ]);
    }

    public function test_host_can_update_conference_stage_layout(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'NextGen Summit',
            'type' => 'town_hall',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'panel_discussions' => true,
                'stage_layout' => 'plenary',
                'panels' => [],
            ]),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'green_room',
            'started_at' => now(),
        ]);

        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
            ->postJson('/room/'.$session->uuid.'/stage', [
                'stage_layout' => 'panel',
                'panels' => [
                    ['name' => 'Opening panel', 'speaker_refs' => [$this->user->public_id]],
                ],
                'active_panel_id' => null,
            ])
            ->assertOk()
            ->assertJson([
                'stage_layout' => 'panel',
                'panel_discussions' => true,
            ]);

        $room->refresh();
        $this->assertSame('panel', $room->setting('stage_layout'));
        $this->assertCount(1, $room->setting('panels'));
    }

    public function test_host_can_spotlight_guest_panelist_by_participant_uuid(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Summit keynote',
            'type' => 'webinar',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'stage_layout' => 'plenary',
            ]),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'started_at' => now(),
        ]);

        $hostParticipant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $guestSpeaker = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'display_name' => 'Guest Speaker',
            'email' => 'speaker@example.com',
            'role' => 'panelist',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $hostParticipant->uuid])
            ->postJson('/room/'.$session->uuid.'/stage', [
                'stage_layout' => 'plenary',
                'spotlight_speaker_ref' => $guestSpeaker->uuid,
            ])
            ->assertOk()
            ->assertJson([
                'stage_layout' => 'plenary',
                'spotlight_speaker_ref' => $guestSpeaker->uuid,
            ]);

        $room->refresh();
        $this->assertSame($guestSpeaker->uuid, $room->setting('spotlight_speaker_ref'));
    }

    public function test_poll_includes_stage_config_for_conference(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'NextGen Summit',
            'type' => 'town_hall',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'panel_discussions' => true,
                'stage_layout' => 'plenary',
            ]),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'live',
            'started_at' => now(),
        ]);

        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
            ->getJson('/room/'.$session->uuid.'/poll')
            ->assertOk()
            ->assertJson([
                'uses_stage_layout' => true,
                'stage_layout' => 'plenary',
                'panel_discussions' => true,
            ]);
    }

    public function test_regular_meeting_rejects_stage_update(): void
    {
        $room = $this->createRoom();
        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'started_at' => now(),
        ]);

        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
            ->postJson('/room/'.$session->uuid.'/stage', [
                'stage_layout' => 'panel',
            ])
            ->assertStatus(422);
    }

    public function test_host_can_create_space_room(): void
    {
        $this->actingAs($this->user)
            ->post('/rooms', [
                'title' => 'Leadership space',
                'invite_only' => true,
            ])
            ->assertRedirect();

        $this->assertDatabaseHas('meet_rooms', [
            'title' => 'Leadership space',
            'type' => 'space',
        ]);
    }

    public function test_space_create_sends_listener_email_invitations(): void
    {
        $this->actingAs($this->user)
            ->post('/rooms', [
                'title' => 'Listener room',
                'invite_emails' => 'listener@example.com, bad-email',
                'invite_only' => true,
            ])
            ->assertSessionHasErrors('invite_emails');

        $this->actingAs($this->user)
            ->post('/rooms', [
                'title' => 'Listener room',
                'invite_emails' => 'listener@example.com, guest@example.com',
                'invite_only' => true,
            ])
            ->assertRedirect();

        $room = Room::where('title', 'Listener room')->firstOrFail();

        $this->assertDatabaseHas('meet_invitations', [
            'room_id' => $room->id,
            'email' => 'listener@example.com',
            'role' => 'attendee',
            'status' => 'pending',
        ]);

        $this->assertDatabaseHas('meet_invitations', [
            'room_id' => $room->id,
            'email' => 'guest@example.com',
            'role' => 'attendee',
        ]);
    }

    public function test_private_space_denies_uninvited_join(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Private space',
            'type' => 'space',
            'status' => 'scheduled',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'audio_only' => true,
                'invite_only' => true,
            ]),
        ]);

        $stranger = User::create([
            'public_id' => 'stranger-001',
            'name' => 'Stranger',
            'email' => 'stranger@example.com',
        ]);

        $this->actingAs($stranger)
            ->get('/r/'.$room->uuid)
            ->assertOk()
            ->assertSee('This room is private');
    }

    public function test_private_space_allows_invited_email_to_join(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Invited space',
            'type' => 'space',
            'status' => 'scheduled',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'audio_only' => true,
                'invite_only' => true,
            ]),
        ]);

        \App\Models\Invitation::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'email' => 'invited@example.com',
            'role' => 'attendee',
            'status' => 'pending',
            'rsvp_token' => 'test-token',
        ]);

        $invited = User::create([
            'public_id' => 'invited-001',
            'name' => 'Invited Listener',
            'email' => 'invited@example.com',
        ]);

        $this->actingAs($invited)
            ->get('/r/'.$room->uuid)
            ->assertOk()
            ->assertDontSee('This room is private');
    }

    public function test_space_host_cannot_create_breakouts(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Leadership space',
            'type' => 'space',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'audio_only' => true,
            ]),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'live',
            'started_at' => now(),
        ]);

        $host = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => 'Host',
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $host->uuid])
            ->postJson('/room/'.$session->uuid.'/breakouts', ['count' => 2])
            ->assertStatus(422);
    }

    public function test_space_live_room_view_omits_meeting_only_features(): void
    {
        config([
            'meet.media.livekit.api_key' => 'APIxxxxxxxxxxxxxxxx',
            'meet.media.livekit.api_secret' => 'secretsecretsecretsecretsecret12',
        ]);

        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Leadership space',
            'type' => 'space',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'audio_only' => true,
            ]),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'live',
            'started_at' => now(),
        ]);

        $host = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => 'Host',
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $response = $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $host->uuid])
            ->get('/room/'.$session->uuid);

        $response->assertOk();
        $response->assertSee('data-is-space="1"', false);
        $response->assertSee('title="Chat"', false);
        $response->assertDontSee('title="People"', false);
        $response->assertDontSee('Start breakouts', false);
        $response->assertDontSee('Share files', false);
        $response->assertDontSee('View the room lineup from Events', false);
    }

    public function test_space_host_can_invite_listener_to_speak_and_listener_accepts(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Leadership space',
            'type' => 'space',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'audio_only' => true,
                'speaker_refs' => [],
            ]),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'live',
            'started_at' => now(),
        ]);

        $host = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => 'Host',
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $listener = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => 'listener-ref',
            'display_name' => 'Listener',
            'role' => 'attendee',
            'status' => 'joined',
            'hand_raised' => true,
            'joined_at' => now(),
        ]);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $host->uuid])
            ->postJson('/room/'.$session->uuid.'/participants/'.$listener->uuid.'/space/speaker')
            ->assertOk()
            ->assertJsonPath('participant.role', 'attendee')
            ->assertJsonPath('participant.speak_requested', true);

        $listener->refresh();
        $this->assertSame('attendee', $listener->role);
        $this->assertTrue($listener->speak_requested);
        $this->assertFalse($listener->hand_raised);

        $this->withSession(["meet.participant.{$session->uuid}" => $listener->uuid])
            ->postJson('/room/'.$session->uuid.'/space/speaker/accept')
            ->assertOk()
            ->assertJsonPath('participant.role', 'panelist')
            ->assertJsonPath('participant.speak_requested', false);

        $listener->refresh();
        $room->refresh();

        $this->assertSame('panelist', $listener->role);
        $this->assertFalse($listener->speak_requested);
        $this->assertContains('listener-ref', (array) $room->setting('speaker_refs', []));
    }

    public function test_space_host_can_make_speaker_co_host_without_acceptance(): void
    {
        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Leadership space',
            'type' => 'space',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'audio_only' => true,
                'speaker_refs' => ['speaker-ref'],
            ]),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'live',
            'started_at' => now(),
        ]);

        $host = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => 'Host',
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $speaker = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => 'speaker-ref',
            'display_name' => 'Speaker',
            'role' => 'panelist',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $host->uuid])
            ->postJson('/room/'.$session->uuid.'/participants/'.$speaker->uuid.'/space/co-host')
            ->assertOk()
            ->assertJsonPath('participant.role', 'co_host');

        $speaker->refresh();
        $this->assertSame('co_host', $speaker->role);
    }

    public function test_breakouts_exclude_host_and_enable_meeting_mode_for_attendees(): void
    {
        config([
            'meet.media.livekit.api_key' => 'APIxxxxxxxxxxxxxxxx',
            'meet.media.livekit.api_secret' => 'secretsecretsecretsecretsecret12',
        ]);

        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Summit',
            'type' => 'town_hall',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => config('meet.default_settings'),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'live',
            'started_at' => now(),
        ]);

        $host = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => 'Host',
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $attendee = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'display_name' => 'Attendee',
            'role' => 'attendee',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $sessions = app(\App\Services\Meet\SessionService::class);
        $this->assertFalse($sessions->canParticipantPublish($attendee));

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $host->uuid])
            ->postJson('/room/'.$session->uuid.'/breakouts', ['count' => 2])
            ->assertOk();

        $host->refresh();
        $attendee->refresh();

        $this->assertNull($host->breakout_room_id);
        $this->assertNotNull($attendee->breakout_room_id);
        $this->assertTrue($sessions->canParticipantPublish($attendee));

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $attendee->uuid])
            ->getJson('/room/'.$session->uuid.'/media-token')
            ->assertOk()
            ->assertJsonPath('in_breakout', true)
            ->assertJsonPath('can_publish', true)
            ->assertJsonPath('uses_stage_layout', false);
    }

    public function test_live_room_shows_programme_when_linked_from_events(): void
    {
        config([
            'meet.media.livekit.api_key' => 'APIxxxxxxxxxxxxxxxx',
            'meet.media.livekit.api_secret' => 'secretsecretsecretsecretsecret12',
        ]);

        $programme = [
            'title' => 'Summit programme',
            'days' => [
                [
                    'label' => 'Day 1',
                    'items' => [
                        ['time' => '09:00', 'title' => 'Opening keynote', 'host' => 'Alex'],
                    ],
                ],
            ],
        ];

        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Summit',
            'type' => 'town_hall',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => array_merge(config('meet.default_settings'), [
                'programme_snapshot' => $programme,
                'linked_programme_items' => [
                    ['time' => '09:00', 'title' => 'Opening keynote', 'host' => 'Alex'],
                ],
            ]),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'live',
            'started_at' => now(),
        ]);

        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
            ->get('/room/'.$session->uuid)
            ->assertOk()
            ->assertSee('Opening keynote', false)
            ->assertSee('Programme', false)
            ->assertSee('meet-icons/programme.svg', false)
            ->assertSee('View the conference lineup from Events', false);
    }

    public function test_live_room_always_lists_programme_in_more_menu_without_linked_data(): void
    {
        config([
            'meet.media.livekit.api_key' => 'APIxxxxxxxxxxxxxxxx',
            'meet.media.livekit.api_secret' => 'secretsecretsecretsecretsecret12',
        ]);

        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Summit',
            'type' => 'town_hall',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => config('meet.default_settings'),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'live',
            'started_at' => now(),
        ]);

        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
            ->get('/room/'.$session->uuid)
            ->assertOk()
            ->assertSee('Programme', false)
            ->assertSee('meet-icons/programme.svg', false)
            ->assertSee('View the conference lineup from Events', false)
            ->assertSee('No programme linked to this conference yet.', false);
    }

    public function test_webinar_live_room_lists_programme_in_more_menu(): void
    {
        config([
            'meet.media.livekit.api_key' => 'APIxxxxxxxxxxxxxxxx',
            'meet.media.livekit.api_secret' => 'secretsecretsecretsecretsecret12',
        ]);

        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Product launch',
            'type' => 'webinar',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => config('meet.default_settings'),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'live',
            'started_at' => now(),
        ]);

        $participant = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => $this->user->name,
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $participant->uuid])
            ->get('/room/'.$session->uuid)
            ->assertOk()
            ->assertSee('View the webinar lineup from Events', false)
            ->assertSee('data-session-noun="webinar"', false);
    }

    public function test_webinar_attendee_can_request_and_receive_speak_access(): void
    {
        config([
            'meet.media.livekit.api_key' => 'APIxxxxxxxxxxxxxxxx',
            'meet.media.livekit.api_secret' => 'secretsecretsecretsecretsecret12',
        ]);

        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Town hall',
            'type' => 'webinar',
            'status' => 'live',
            'scheduled_at' => now()->addHour(),
            'timezone' => 'UTC',
            'settings' => config('meet.default_settings'),
        ]);

        $session = Session::create([
            'owner_ref' => $this->user->public_id,
            'room_id' => $room->id,
            'media_room_name' => 'meet-'.$room->uuid,
            'status' => 'live',
            'session_mode' => 'live',
            'started_at' => now(),
        ]);

        $host = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'user_ref' => $this->user->public_id,
            'display_name' => 'Host',
            'role' => 'host',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $attendee = \App\Models\Participant::create([
            'owner_ref' => $this->user->public_id,
            'session_id' => $session->id,
            'display_name' => 'Attendee',
            'role' => 'attendee',
            'status' => 'joined',
            'joined_at' => now(),
        ]);

        $sessions = app(\App\Services\Meet\SessionService::class);
        $this->assertFalse($sessions->canParticipantPublish($attendee));

        $this->withSession(["meet.participant.{$session->uuid}" => $attendee->uuid])
            ->get('/room/'.$session->uuid)
            ->assertOk()
            ->assertSee('Request to speak', false)
            ->assertSee('meet-icons/speak.svg', false)
            ->assertSee('data-uses-speak-access="1"', false);

        $this->withSession(["meet.participant.{$session->uuid}" => $attendee->uuid])
            ->postJson(route('meet.room.speak.request', $session))
            ->assertOk()
            ->assertJsonPath('participant.speak_requested', true);

        $attendee->refresh();
        $this->assertTrue($attendee->speak_requested);
        $this->assertFalse($attendee->speak_granted);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $host->uuid])
            ->postJson(route('meet.room.speak.grant', [$session, $attendee]))
            ->assertOk()
            ->assertJsonPath('participant.speak_granted', true)
            ->assertJsonPath('participant.speak_requested', false);

        $attendee->refresh();
        $this->assertTrue($attendee->speak_granted);
        $this->assertTrue($sessions->canParticipantPublish($attendee));

        $mediaState = $sessions->mediaSessionState($attendee);
        $this->assertTrue($mediaState['can_publish']);
        $this->assertTrue($mediaState['audio_only_publish']);
        $this->assertTrue($mediaState['speak_granted']);

        $this->withSession(["meet.participant.{$session->uuid}" => $attendee->uuid])
            ->get('/room/'.$session->uuid)
            ->assertOk()
            ->assertSee('You can speak', false)
            ->assertSee('data-speak-granted="1"', false);

        $this->actingAs($this->user)
            ->withSession(["meet.participant.{$session->uuid}" => $host->uuid])
            ->postJson(route('meet.room.speak.revoke', [$session, $attendee]))
            ->assertOk()
            ->assertJsonPath('participant.speak_granted', false);

        $attendee->refresh();
        $this->assertFalse($sessions->canParticipantPublish($attendee));
    }

    public function test_webinar_show_events_create_link_uses_events_create_path(): void
    {
        config([
            'meet.events_app_url' => 'https://events.test',
            'meet.events_api_url' => 'https://events.test/api/service/v1',
            'meet.events_api_key' => '',
        ]);

        $room = Room::create([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Sales webinar',
            'type' => 'webinar',
            'status' => 'ended',
            'scheduled_at' => now()->subDay(),
            'timezone' => 'UTC',
            'settings' => config('meet.default_settings'),
        ]);

        $this->actingAs($this->user)
            ->get(route('meet.webinars.show', $room))
            ->assertOk()
            ->assertSee('https://events.test/events/create', false)
            ->assertSee('Events linking is not configured on this Meet instance.', false)
            ->assertDontSee('/qr-codes/create', false);
    }

    public function test_afia_chat_returns_reply_when_ai_configured(): void
    {
        config([
            'afia.api_key' => 'test-openai-key',
            'afia.platform_api_key' => '',
        ]);

        Http::fake([
            'api.openai.com/*' => Http::response([
                'choices' => [['message' => ['content' => 'Go to Meetings and click Schedule meeting.']]],
            ]),
        ]);

        $this->createRoom(['title' => 'Upcoming sync']);

        $this->actingAs($this->user)
            ->postJson('/ai/chat', ['message' => 'How do I schedule a meeting?'])
            ->assertOk()
            ->assertJson(['reply' => 'Go to Meetings and click Schedule meeting.']);
    }

    protected function createRoom(array $overrides = []): Room
    {
        return Room::create(array_merge([
            'owner_ref' => $this->user->public_id,
            'organization_id' => $this->organization->id,
            'host_user_ref' => $this->user->public_id,
            'title' => 'Test Meeting',
            'type' => 'scheduled',
            'status' => 'scheduled',
            'scheduled_at' => now()->addDay(),
            'timezone' => 'UTC',
            'settings' => config('meet.default_settings'),
        ], $overrides));
    }
}
