<?php

namespace Tests\Feature\Api;

use App\Mail\LineupUserNotificationMail;
use App\Models\Mission;
use App\Models\MissionStatusEvent;
use App\Models\Payment;
use App\Models\Payout;
use App\Models\User;
use App\Services\Auth\FirebaseIdTokenVerifier;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Password;
use Tests\TestCase;

class LineupApiTest extends TestCase
{
    use RefreshDatabase;

    public function test_register_and_me_endpoints_work_with_bearer_token(): void
    {
        $register = $this->postJson('/api/auth/register', [
            'name' => 'Client 1',
            'email' => 'client1@example.com',
            'password' => 'password123',
            'role' => 'client',
        ]);

        $register->assertCreated();
        $token = $register->json('token');

        $this->assertNotEmpty($token);

        $this->getJson('/api/auth/me', [
            'Authorization' => 'Bearer ' . $token,
        ])->assertOk()->assertJsonPath('user.role', 'client');
    }

    public function test_user_can_login_with_firebase_token_for_expected_role(): void
    {
        $this->mock(FirebaseIdTokenVerifier::class, function ($mock): void {
            $mock->shouldReceive('verify')->once()->andReturn([
                'sub' => 'firebase_uid_client_1',
                'user_id' => 'firebase_uid_client_1',
                'email' => 'firebase-client@example.com',
                'email_verified' => true,
                'name' => 'Firebase Client',
                'firebase' => [
                    'sign_in_provider' => 'google.com',
                ],
            ]);
        });

        $response = $this->postJson('/api/auth/firebase', [
            'id_token' => 'fake.token.payload',
            'role' => 'client',
            'provider' => 'google.com',
        ])->assertOk();

        $this->assertNotEmpty($response->json('token'));
        $this->assertSame('client', $response->json('user.role'));
        $this->assertSame('firebase_uid_client_1', $response->json('user.firebase_uid'));
    }

    public function test_user_can_update_own_profile_from_api(): void
    {
        $token = $this->registerAndGetToken('liner', 'liner-profile@example.com');

        $this->patchJson('/api/auth/me', [
            'name' => 'Liner Updated',
            'phone' => '+33600000000',
            'bio' => 'Disponible week-end',
            'avatar_url' => 'https://example.com/avatar.png',
            'is_available' => false,
        ], [
            'Authorization' => 'Bearer ' . $token,
        ])->assertOk()
            ->assertJsonPath('user.name', 'Liner Updated')
            ->assertJsonPath('user.phone', '+33600000000')
            ->assertJsonPath('user.bio', 'Disponible week-end')
            ->assertJsonPath('user.is_available', false);

        $this->assertDatabaseHas('users', [
            'email' => 'liner-profile@example.com',
            'name' => 'Liner Updated',
            'phone' => '+33600000000',
            'bio' => 'Disponible week-end',
            'is_available' => 0,
        ]);
    }

    public function test_client_can_update_locale_and_notification_preferences_from_api(): void
    {
        $token = $this->registerAndGetToken('client', 'client-preferences@example.com');

        $this->patchJson('/api/auth/me', [
            'language' => 'en',
            'region' => 'sn',
            'push_notifications_enabled' => false,
            'sms_notifications_enabled' => true,
            'email_notifications_enabled' => false,
            'email_marketing_enabled' => false,
        ], [
            'Authorization' => 'Bearer ' . $token,
        ])->assertOk()
            ->assertJsonPath('user.language', 'en')
            ->assertJsonPath('user.region', 'SN')
            ->assertJsonPath('user.push_notifications_enabled', false)
            ->assertJsonPath('user.sms_notifications_enabled', true)
            ->assertJsonPath('user.email_notifications_enabled', false)
            ->assertJsonPath('user.email_marketing_enabled', false);

        $this->assertDatabaseHas('users', [
            'email' => 'client-preferences@example.com',
            'language' => 'en',
            'region' => 'SN',
            'push_notifications_enabled' => 0,
            'sms_notifications_enabled' => 1,
            'email_notifications_enabled' => 0,
            'email_marketing_enabled' => 0,
        ]);
    }

    public function test_user_can_delete_own_account_from_api(): void
    {
        $email = 'client-delete-me@example.com';
        $token = $this->registerAndGetToken('client', $email);

        $this->deleteJson('/api/auth/me', [], [
            'Authorization' => 'Bearer ' . $token,
        ])->assertNoContent();

        $this->assertDatabaseMissing('users', [
            'email' => $email,
        ]);

        $this->getJson('/api/auth/me', [
            'Authorization' => 'Bearer ' . $token,
        ])->assertUnauthorized();
    }

    public function test_forgot_password_sends_reset_link_notification(): void
    {
        $user = User::factory()->create([
            'email' => 'forgot-password@example.com',
            'password' => 'password123',
        ]);

        Notification::fake();

        $this->postJson('/api/auth/forgot-password', [
            'email' => $user->email,
        ])->assertAccepted();

        Notification::assertSentTo($user, ResetPassword::class);
    }

    public function test_user_can_reset_password_via_api(): void
    {
        $user = User::factory()->create([
            'email' => 'reset-password@example.com',
            'password' => 'old-password-123',
        ]);

        $token = Password::broker()->createToken($user);

        $this->postJson('/api/auth/reset-password', [
            'email' => $user->email,
            'token' => $token,
            'password' => 'new-password-123',
            'password_confirmation' => 'new-password-123',
        ])->assertOk();

        $this->postJson('/api/auth/login', [
            'login' => $user->email,
            'password' => 'old-password-123',
        ])->assertStatus(422);

        $this->postJson('/api/auth/login', [
            'login' => $user->email,
            'password' => 'new-password-123',
        ])->assertOk();
    }

    public function test_client_can_create_and_liner_can_complete_mission_with_wallet_credit(): void
    {
        $clientToken = $this->registerAndGetToken('client', 'client2@example.com');
        $linerToken = $this->registerAndGetToken('liner', 'liner2@example.com');
        $paymentMethodId = $this->attachDefaultPayInMethod($clientToken);

        $createMission = $this->postJson(
            '/api/missions',
            [
                'title' => 'Mission test',
                'location' => 'Paris',
                'expected_start_at' => now()->addHour()->toISOString(),
                'budget_amount' => 60,
            ],
            ['Authorization' => 'Bearer ' . $clientToken]
        );

        $createMission->assertCreated();
        $missionId = $createMission->json('data.id');

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $this->postJson("/api/missions/{$missionId}/confirm-liner", [
            'payment_method_id' => $paymentMethodId,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk();

        $this->postJson(
            "/api/missions/{$missionId}/status",
            ['status' => 'EN_ROUTE'],
            ['Authorization' => 'Bearer ' . $linerToken]
        )->assertOk();

        $this->postJson(
            "/api/missions/{$missionId}/status",
            ['status' => 'MISSION_TERMINEE'],
            ['Authorization' => 'Bearer ' . $linerToken]
        )->assertOk();

        $wallet = $this->getJson('/api/wallet', [
            'Authorization' => 'Bearer ' . $linerToken,
        ]);

        $wallet->assertOk();
        $this->assertSame('58.50', $wallet->json('data.balance'));

        $payments = $this->getJson('/api/payments/history', [
            'Authorization' => 'Bearer ' . $clientToken,
        ]);

        $payments->assertOk();
        $this->assertSame('paid', $payments->json('data.data.0.status'));
    }

    public function test_client_can_select_only_one_liner_when_multiple_liners_apply(): void
    {
        $clientToken = $this->registerAndGetToken('client', 'client-multi@example.com');
        $liner1Token = $this->registerAndGetToken('liner', 'liner-multi-1@example.com');
        $liner2Token = $this->registerAndGetToken('liner', 'liner-multi-2@example.com');
        $paymentMethodId = $this->attachDefaultPayInMethod($clientToken);

        $createMission = $this->postJson(
            '/api/missions',
            [
                'title' => 'Mission multi candidatures',
                'location' => 'Paris',
                'expected_start_at' => now()->addHour()->toISOString(),
                'budget_amount' => 42,
            ],
            ['Authorization' => 'Bearer ' . $clientToken]
        )->assertCreated();

        $missionId = (int) $createMission->json('data.id');
        $liner1Id = (int) User::query()->where('email', 'liner-multi-1@example.com')->value('id');
        $liner2Id = (int) User::query()->where('email', 'liner-multi-2@example.com')->value('id');

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $liner1Token,
        ])->assertOk();

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $liner2Token,
        ])->assertOk();

        $this->assertDatabaseHas('mission_applications', [
            'mission_id' => $missionId,
            'liner_id' => $liner1Id,
            'status' => 'pending',
        ]);
        $this->assertDatabaseHas('mission_applications', [
            'mission_id' => $missionId,
            'liner_id' => $liner2Id,
            'status' => 'pending',
        ]);

        $this->postJson("/api/missions/{$missionId}/confirm-liner", [
            'payment_method_id' => $paymentMethodId,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertStatus(422);

        $this->postJson("/api/missions/{$missionId}/choose-liner", [
            'liner_id' => $liner1Id,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk()
            ->assertJsonPath('data.status', 'accepted')
            ->assertJsonPath('data.liner_id', $liner1Id);

        $this->assertDatabaseHas('mission_applications', [
            'mission_id' => $missionId,
            'liner_id' => $liner1Id,
            'status' => 'accepted',
        ]);
        $this->assertDatabaseHas('mission_applications', [
            'mission_id' => $missionId,
            'liner_id' => $liner2Id,
            'status' => 'rejected',
        ]);

        $confirm = $this->postJson("/api/missions/{$missionId}/confirm-liner", [
            'payment_method_id' => $paymentMethodId,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ]);

        $this->assertTrue(in_array($confirm->getStatusCode(), [200, 202], true));
        $this->assertDatabaseHas('missions', [
            'id' => $missionId,
            'liner_id' => $liner1Id,
            'status' => 'accepted',
        ]);
    }

    public function test_admin_can_review_proofs_and_disputes(): void
    {
        $admin = User::factory()->create([
            'email' => 'admin-api@example.com',
            'password' => 'password123',
            'role' => 'admin',
        ]);

        $clientToken = $this->registerAndGetToken('client', 'client3@example.com');
        $linerToken = $this->registerAndGetToken('liner', 'liner3@example.com');

        $createMission = $this->postJson(
            '/api/missions',
            [
                'title' => 'Mission moderation',
                'location' => 'Lyon',
                'expected_start_at' => now()->addHour()->toISOString(),
                'budget_amount' => 45,
            ],
            ['Authorization' => 'Bearer ' . $clientToken]
        );

        $missionId = $createMission->json('data.id');

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $proof = $this->postJson(
            "/api/missions/{$missionId}/proof",
            [
                'type' => 'photo',
                'proof_url' => 'https://example.com/proof.jpg',
            ],
            ['Authorization' => 'Bearer ' . $linerToken]
        )->assertCreated();

        $proofId = $proof->json('data.id');

        $dispute = $this->postJson(
            "/api/missions/{$missionId}/disputes",
            [
                'category' => 'qualite',
                'description' => 'Photo insuffisante.',
            ],
            ['Authorization' => 'Bearer ' . $clientToken]
        )->assertCreated();

        $disputeId = $dispute->json('data.id');

        $adminLogin = $this->postJson('/api/auth/login', [
            'login' => $admin->email,
            'password' => 'password123',
        ])->assertOk();

        $adminToken = $adminLogin->json('token');

        $this->patchJson(
            "/api/admin/proofs/{$proofId}",
            ['status' => 'approved'],
            ['Authorization' => 'Bearer ' . $adminToken]
        )->assertOk();

        $this->patchJson(
            "/api/admin/disputes/{$disputeId}",
            ['status' => 'resolved', 'resolution_notes' => 'Cas traite'],
            ['Authorization' => 'Bearer ' . $adminToken]
        )->assertOk();

        $this->assertDatabaseHas('mission_proofs', [
            'id' => $proofId,
            'status' => 'approved',
        ]);

        $this->assertDatabaseHas('mission_disputes', [
            'id' => $disputeId,
            'status' => 'resolved',
        ]);
    }

    public function test_notifications_flow_for_client_and_liner(): void
    {
        $clientToken = $this->registerAndGetToken('client', 'client4@example.com');
        $linerToken = $this->registerAndGetToken('liner', 'liner4@example.com');
        $paymentMethodId = $this->attachDefaultPayInMethod($clientToken);

        $createMission = $this->postJson(
            '/api/missions',
            [
                'title' => 'Mission notif',
                'location' => 'Marseille',
                'expected_start_at' => now()->addHour()->toISOString(),
                'budget_amount' => 72,
            ],
            ['Authorization' => 'Bearer ' . $clientToken]
        )->assertCreated();

        $missionId = $createMission->json('data.id');

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $this->postJson("/api/missions/{$missionId}/confirm-liner", [
            'payment_method_id' => $paymentMethodId,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk();

        $clientNotifications = $this->getJson('/api/notifications', [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk();

        $acceptNotification = collect($clientNotifications->json('data.data'))
            ->first(
                fn (array $row): bool => in_array(
                    ($row['type'] ?? null),
                    ['mission.application_submitted', 'mission.accepted'],
                    true
                )
            );

        $this->assertNotNull($acceptNotification);

        $this->postJson('/api/missions/' . $missionId . '/status', [
            'status' => 'MISSION_TERMINEE',
        ], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $linerNotifications = $this->getJson('/api/notifications', [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $paymentNotification = collect($linerNotifications->json('data.data'))
            ->first(fn (array $row): bool => in_array(($row['type'] ?? null), ['payment.validated', 'mission.client_validated'], true));

        $this->assertNotNull($paymentNotification);

        $markRead = $this->postJson(
            '/api/notifications/' . $acceptNotification['id'] . '/read',
            [],
            ['Authorization' => 'Bearer ' . $clientToken]
        )->assertOk();

        $this->assertNotNull($markRead->json('data.read_at'));

        $this->postJson('/api/notifications/read-all', [], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertNoContent();
    }

    public function test_user_can_register_and_delete_device_token(): void
    {
        $clientToken = $this->registerAndGetToken('client', 'client5@example.com');

        $create = $this->postJson('/api/devices/tokens', [
            'platform' => 'android',
            'token' => 'fcm-device-token-xyz',
            'device_name' => 'Pixel Test',
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertCreated();

        $deviceTokenId = $create->json('data.id');

        $this->getJson('/api/devices/tokens', [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk()->assertJsonPath('data.data.0.token', 'fcm-device-token-xyz');

        $this->deleteJson('/api/devices/tokens/' . $deviceTokenId, [], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertNoContent();
    }

    public function test_mission_stream_endpoint_is_accessible_for_participant(): void
    {
        $clientToken = $this->registerAndGetToken('client', 'client6@example.com');
        $linerToken = $this->registerAndGetToken('liner', 'liner6@example.com');
        $paymentMethodId = $this->attachDefaultPayInMethod($clientToken);

        $createMission = $this->postJson(
            '/api/missions',
            [
                'title' => 'Mission stream',
                'location' => 'Nice',
                'expected_start_at' => now()->addHour()->toISOString(),
                'budget_amount' => 35,
            ],
            ['Authorization' => 'Bearer ' . $clientToken]
        )->assertCreated();

        $missionId = $createMission->json('data.id');

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $this->postJson("/api/missions/{$missionId}/confirm-liner", [
            'payment_method_id' => $paymentMethodId,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk();

        $this->postJson("/api/missions/{$missionId}/status", [
            'status' => 'EN_ROUTE',
        ], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $stream = $this->get('/api/missions/' . $missionId . '/stream?since_id=0&timeout=1', [
            'Authorization' => 'Bearer ' . $clientToken,
            'Accept' => 'text/event-stream',
        ]);

        $stream->assertOk();
        $this->assertStringContainsString(
            'text/event-stream',
            (string) $stream->headers->get('content-type')
        );
    }

    public function test_client_and_liner_can_chat_and_liner_can_push_live_location(): void
    {
        $clientToken = $this->registerAndGetToken('client', 'client-chat@example.com');
        $linerToken = $this->registerAndGetToken('liner', 'liner-chat@example.com');

        $createMission = $this->postJson(
            '/api/missions',
            [
                'title' => 'Mission chat live',
                'location' => 'Paris',
                'expected_start_at' => now()->addHour()->toISOString(),
                'budget_amount' => 55,
            ],
            ['Authorization' => 'Bearer ' . $clientToken]
        )->assertCreated();

        $missionId = $createMission->json('data.id');

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $this->postJson("/api/missions/{$missionId}/location", [
            'latitude' => 48.8566,
            'longitude' => 2.3522,
            'note' => 'Position live',
        ], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk()->assertJsonPath('data.ok', true);

        $locationEvent = MissionStatusEvent::query()
            ->where('mission_id', $missionId)
            ->where('status', 'live_location')
            ->latest('id')
            ->first();

        $this->assertNotNull($locationEvent);
        $this->assertEqualsWithDelta(48.8566, (float) $locationEvent->latitude, 0.00001);
        $this->assertEqualsWithDelta(2.3522, (float) $locationEvent->longitude, 0.00001);

        $this->postJson("/api/missions/{$missionId}/messages", [
            'message' => 'Bonjour, je suis le client.',
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertCreated();

        $this->postJson("/api/missions/{$missionId}/messages", [
            'message' => 'Bien recu, je suis en route.',
        ], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertCreated();

        $messages = $this->getJson("/api/missions/{$missionId}/messages", [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk()->assertJsonCount(2, 'data');

        $messageTexts = collect($messages->json('data'))->pluck('message')->all();
        $this->assertContains('Bonjour, je suis le client.', $messageTexts);
        $this->assertContains('Bien recu, je suis en route.', $messageTexts);

        $track = $this->getJson("/api/missions/{$missionId}/track", [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk();

        $timeline = collect($track->json('data.timeline'));
        $this->assertTrue(
            $timeline->contains(
                fn (array $event): bool => ($event['status'] ?? null) === 'live_location'
            )
        );
        $track->assertJsonCount(2, 'data.messages');
    }

    public function test_admin_can_send_push_test_notification(): void
    {
        $admin = User::factory()->create([
            'email' => 'admin-push@example.com',
            'password' => 'password123',
            'role' => 'admin',
        ]);

        $this->registerAndGetToken('client', 'client-push-target@example.com');
        $target = User::query()->where('email', 'client-push-target@example.com')->firstOrFail();

        $adminToken = $this->postJson('/api/auth/login', [
            'login' => $admin->email,
            'password' => 'password123',
        ])->assertOk()->json('token');

        $this->postJson('/api/admin/push/test', [
            'user_id' => $target->id,
            'title' => 'Test push',
            'body' => 'Message push test',
        ], [
            'Authorization' => 'Bearer ' . $adminToken,
        ])->assertAccepted();

        $this->assertDatabaseHas('user_notifications', [
            'user_id' => $target->id,
            'type' => 'admin.push_test',
            'title' => 'Test push',
        ]);
    }

    public function test_client_can_manage_pay_in_methods_and_settlement_uses_default_provider(): void
    {
        $clientToken = $this->registerAndGetToken('client', 'client-payin@example.com');

        $payInMethod = $this->postJson('/api/payment-methods', [
            'type' => 'pay_in',
            'provider' => 'paypal',
            'label' => 'PayPal principal',
            'details' => [
                'email' => 'client-payin@example.com',
                'vault_token' => 'BA-TEST-VAULT-001',
                'vault_token_type' => 'BILLING_AGREEMENT',
            ],
            'is_default' => true,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertCreated();

        $methodId = $payInMethod->json('data.id');

        $this->postJson('/api/payment-methods', [
            'type' => 'pay_out',
            'provider' => 'rib',
            'details' => [
                'iban' => 'FR7611119000694100000000000',
            ],
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertForbidden();

        $this->patchJson('/api/payment-methods/' . $methodId, [
            'label' => 'PayPal Business',
            'is_default' => true,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk()
            ->assertJsonPath('data.label', 'PayPal Business')
            ->assertJsonPath('data.is_default', true);

        $this->assertDatabaseHas('user_payment_methods', [
            'id' => $methodId,
            'type' => 'pay_in',
            'provider' => 'paypal',
            'is_default' => 1,
            'is_active' => 1,
        ]);

        $linerToken = $this->registerAndGetToken('liner', 'liner-payin-target@example.com');

        $createMission = $this->postJson(
            '/api/missions',
            [
                'title' => 'Mission provider settlement',
                'location' => 'Paris',
                'expected_start_at' => now()->addHour()->toISOString(),
                'budget_amount' => 40,
            ],
            ['Authorization' => 'Bearer ' . $clientToken]
        )->assertCreated();

        $missionId = $createMission->json('data.id');

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $this->postJson("/api/missions/{$missionId}/confirm-liner", [
            'payment_method_id' => $methodId,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertAccepted();

        $this->postJson("/api/missions/{$missionId}/status", [
            'status' => 'MISSION_TERMINEE',
        ], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $payments = $this->getJson('/api/payments/history', [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk();

        $this->assertSame('paypal', $payments->json('data.data.0.provider'));
        $this->assertSame($methodId, $payments->json('data.data.0.payment_method_id'));
    }

    public function test_client_can_setup_and_capture_paypal_vault_order(): void
    {
        $clientToken = $this->registerAndGetToken('client', 'client-paypal-vault@example.com');

        $setup = $this->postJson('/api/payments/paypal/vault/setup-order', [
            'amount' => 1,
            'currency' => 'EUR',
            'usage_pattern' => 'UNSCHEDULED_POSTPAID',
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertCreated();

        $orderId = (string) $setup->json('data.order_id');
        $this->assertNotSame('', $orderId);

        $capture = $this->postJson('/api/payments/paypal/vault/capture-order', [
            'order_id' => $orderId,
            'label' => 'PayPal auto-debit',
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertCreated();

        $this->assertNotEmpty($capture->json('data.capture.vault_id'));

        $this->assertDatabaseHas('user_payment_methods', [
            'user_id' => User::query()->where('email', 'client-paypal-vault@example.com')->value('id'),
            'type' => 'pay_in',
            'provider' => 'paypal',
            'is_active' => 1,
        ]);
    }

    public function test_paypal_pay_in_requires_vault_token(): void
    {
        $clientToken = $this->registerAndGetToken('client', 'client-paypal-missing-vault@example.com');

        $this->postJson('/api/payment-methods', [
            'type' => 'pay_in',
            'provider' => 'paypal',
            'label' => 'PayPal incomplet',
            'details' => [
                'email' => 'client-paypal-missing-vault@example.com',
            ],
            'is_default' => true,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertStatus(422)
            ->assertJsonValidationErrors(['details.vault_token']);
    }

    public function test_liner_can_manage_payout_methods_and_withdraw_with_selected_method(): void
    {
        $linerToken = $this->registerAndGetToken('liner', 'liner-payout@example.com');

        $payoutMethod = $this->postJson('/api/payment-methods', [
            'type' => 'pay_out',
            'provider' => 'rib',
            'label' => 'Compte principal',
            'details' => [
                'iban' => 'FR7611119000694100000000000',
            ],
        ], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertCreated();

        $this->assertTrue((bool) $payoutMethod->json('data.is_default'));
        $methodId = $payoutMethod->json('data.id');

        $clientToken = $this->registerAndGetToken('client', 'client-payout-source@example.com');

        $this->postJson('/api/payment-methods', [
            'type' => 'pay_in',
            'provider' => 'stripe',
            'details' => [
                'reference' => 'pm_test_123',
            ],
            'is_default' => true,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertCreated();

        $mission = $this->postJson('/api/missions', [
            'title' => 'Mission payout withdraw',
            'location' => 'Lille',
            'expected_start_at' => now()->addHour()->toISOString(),
            'budget_amount' => 90,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertCreated();

        $missionId = $mission->json('data.id');

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $this->postJson("/api/missions/{$missionId}/confirm-liner", [], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk();

        $this->postJson("/api/missions/{$missionId}/status", [
            'status' => 'MISSION_TERMINEE',
        ], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $this->postJson('/api/wallet/withdraw', [
            'amount' => 35,
            'payment_method_id' => $methodId,
        ], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertAccepted()->assertJsonPath('data.provider', 'rib');

        $this->assertDatabaseHas('payouts', [
            'liner_id' => User::where('email', 'liner-payout@example.com')->value('id'),
            'payment_method_id' => $methodId,
            'provider' => 'rib',
            'status' => 'pending',
        ]);
    }

    public function test_stripe_webhook_marks_payment_as_paid_and_credits_wallet(): void
    {
        $this->registerAndGetToken('client', 'client-webhook@example.com');
        $this->registerAndGetToken('liner', 'liner-webhook@example.com');

        $client = User::query()->where('email', 'client-webhook@example.com')->firstOrFail();
        $liner = User::query()->where('email', 'liner-webhook@example.com')->firstOrFail();

        $mission = Mission::create([
            'client_id' => $client->id,
            'liner_id' => $liner->id,
            'title' => 'Mission webhook stripe',
            'location' => 'Paris',
            'expected_start_at' => now()->addHour(),
            'status' => 'completed',
            'budget_amount' => 33,
            'currency' => 'EUR',
            'completed_at' => now(),
        ]);

        $payment = Payment::create([
            'mission_id' => $mission->id,
            'client_id' => $client->id,
            'liner_id' => $liner->id,
            'amount' => 33,
            'currency' => 'EUR',
            'status' => 'pending',
            'provider' => 'stripe',
            'provider_reference' => 'pi_test_123',
        ]);

        $this->postJson('/api/stripe/webhook', [
            'id' => 'evt_test_1',
            'type' => 'payment_intent.succeeded',
            'data' => [
                'object' => [
                    'id' => 'pi_test_123',
                ],
            ],
        ])->assertAccepted();

        $this->assertDatabaseHas('payments', [
            'id' => $payment->id,
            'status' => 'paid',
            'provider_reference' => 'pi_test_123',
        ]);

        $this->assertDatabaseHas('wallet_transactions', [
            'reference_type' => Payment::class,
            'reference_id' => $payment->id,
            'type' => 'credit',
        ]);
    }

    public function test_paypal_webhook_marks_payout_as_paid(): void
    {
        $this->registerAndGetToken('liner', 'liner-paypal-webhook@example.com');
        $liner = User::query()->where('email', 'liner-paypal-webhook@example.com')->firstOrFail();

        $payout = Payout::create([
            'liner_id' => $liner->id,
            'amount' => 45,
            'currency' => 'EUR',
            'provider' => 'paypal',
            'provider_reference' => 'batch_test_456',
            'status' => 'pending',
            'requested_at' => now(),
        ]);

        $this->postJson('/api/webhooks/paypal', [
            'id' => 'WH-TEST-1',
            'event_type' => 'PAYMENT.PAYOUTSBATCH.SUCCESS',
            'resource' => [
                'payout_batch_id' => 'batch_test_456',
            ],
        ])->assertAccepted();

        $this->assertDatabaseHas('payouts', [
            'id' => $payout->id,
            'status' => 'paid',
            'provider_reference' => 'batch_test_456',
        ]);
    }

    public function test_liner_without_kyc_cannot_accept_mission(): void
    {
        $clientToken = $this->registerAndGetToken('client', 'client-no-kyc@example.com');
        $linerToken = $this->registerAndGetToken('liner', 'liner-no-kyc@example.com', false);

        $missionId = $this->postJson('/api/missions', [
            'title' => 'Mission KYC required',
            'location' => 'Paris',
            'expected_start_at' => now()->addHour()->toISOString(),
            'budget_amount' => 30,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertCreated()->json('data.id');

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertStatus(422);
    }

    public function test_liner_kyc_submission_requires_allowed_document_type_and_url(): void
    {
        $linerToken = $this->registerAndGetToken('liner', 'liner-kyc-upload@example.com', false);

        $this->postJson('/api/kyc/documents', [
            'document_type' => 'driving_license',
            'document_number' => 'ABC123',
            'document_url' => 'https://example.com/doc.jpg',
        ], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertStatus(422);

        $this->postJson('/api/kyc/documents', [
            'document_type' => 'cni',
            'document_number' => 'ABC123',
        ], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertStatus(422);
    }

    public function test_available_liner_can_push_live_location_without_mission(): void
    {
        $linerEmail = 'liner-live-presence@example.com';
        $linerToken = $this->registerAndGetToken('liner', $linerEmail);

        $this->postJson('/api/auth/location', [
            'latitude' => 48.8566,
            'longitude' => 2.3522,
        ], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk()->assertJsonPath('data.ok', true);

        $this->assertDatabaseHas('users', [
            'email' => $linerEmail,
            'current_latitude' => '48.8566000',
            'current_longitude' => '2.3522000',
        ]);
    }

    public function test_notifications_are_sent_by_email_to_client_and_liner(): void
    {
        Mail::fake();

        $clientEmail = 'client-mail@example.com';
        $linerEmail = 'liner-mail@example.com';

        $clientToken = $this->registerAndGetToken('client', $clientEmail);
        $linerToken = $this->registerAndGetToken('liner', $linerEmail);
        $paymentMethodId = $this->attachDefaultPayInMethod($clientToken);

        $missionId = $this->postJson('/api/missions', [
            'title' => 'Mission email notif',
            'location' => 'Paris',
            'expected_start_at' => now()->addHour()->toISOString(),
            'budget_amount' => 50,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertCreated()->json('data.id');

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $this->postJson("/api/missions/{$missionId}/confirm-liner", [
            'payment_method_id' => $paymentMethodId,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk();

        Mail::assertSent(LineupUserNotificationMail::class, function (LineupUserNotificationMail $mail) use ($clientEmail): bool {
            return $mail->hasTo($clientEmail);
        });

        Mail::assertSent(LineupUserNotificationMail::class, function (LineupUserNotificationMail $mail) use ($linerEmail): bool {
            return $mail->hasTo($linerEmail);
        });
    }

    public function test_user_can_disable_email_notifications_preference(): void
    {
        Mail::fake();

        $clientEmail = 'client-mail-disabled@example.com';
        $linerEmail = 'liner-mail-enabled@example.com';

        $clientToken = $this->registerAndGetToken('client', $clientEmail);
        $linerToken = $this->registerAndGetToken('liner', $linerEmail);
        $paymentMethodId = $this->attachDefaultPayInMethod($clientToken);

        $this->patchJson('/api/auth/me', [
            'name' => 'Client User',
            'email_notifications_enabled' => false,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk()->assertJsonPath('user.email_notifications_enabled', false);

        $missionId = $this->postJson('/api/missions', [
            'title' => 'Mission prefs email',
            'location' => 'Paris',
            'expected_start_at' => now()->addHour()->toISOString(),
            'budget_amount' => 50,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertCreated()->json('data.id');

        $this->postJson("/api/missions/{$missionId}/accept", [], [
            'Authorization' => 'Bearer ' . $linerToken,
        ])->assertOk();

        $this->postJson("/api/missions/{$missionId}/confirm-liner", [
            'payment_method_id' => $paymentMethodId,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertOk();

        Mail::assertSent(LineupUserNotificationMail::class, function (LineupUserNotificationMail $mail) use ($linerEmail): bool {
            return $mail->hasTo($linerEmail);
        });

        Mail::assertNotSent(LineupUserNotificationMail::class, function (LineupUserNotificationMail $mail) use ($clientEmail): bool {
            return $mail->hasTo($clientEmail);
        });
    }

    private function registerAndGetToken(string $role, string $email, bool $verifyLiner = true): string
    {
        $response = $this->postJson('/api/auth/register', [
            'name' => ucfirst($role) . ' User',
            'email' => $email,
            'password' => 'password123',
            'role' => $role,
        ]);

        $response->assertCreated();

        $this->assertDatabaseHas('users', [
            'email' => $email,
            'role' => $role,
        ]);

        if ($role === 'liner' && $verifyLiner) {
            User::query()
                ->where('email', $email)
                ->update([
                    'is_kyc_verified' => true,
                    'is_available' => true,
                ]);
        }

        return (string) $response->json('token');
    }

    private function attachDefaultPayInMethod(string $clientToken): int
    {
        return (int) $this->postJson('/api/payment-methods', [
            'type' => 'pay_in',
            'provider' => 'stripe',
            'details' => [
                'reference' => 'pm_test_default',
            ],
            'is_default' => true,
        ], [
            'Authorization' => 'Bearer ' . $clientToken,
        ])->assertCreated()->json('data.id');
    }
}
