<?php

namespace Tests\Feature;

use App\Enums\ApprovalStatusEnum;
use App\Enums\PushNotificationCategoryEnum;
use App\Enums\PushNotificationTargetEnum;
use App\Enums\UserTypeEnum;
use App\Models\DeviceToken;
use App\Models\Provider;
use App\Models\PushNotificationLog;
use App\Models\User;
use App\Notifications\Push\Prestador\Transacional\CadastroAprovadoPush;
use App\Services\ProviderService;
use App\Services\PushNotificationService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Mockery\MockInterface;
use Tests\TestCase;

class ProviderApprovalPushTest extends TestCase
{
    use RefreshDatabase;

    private ProviderService $service;

    protected function setUp(): void
    {
        parent::setUp();

        Mail::fake();

        $this->service = app(ProviderService::class);
    }

    public function test_aprovar_prestador_dispara_push_de_cadastro_aprovado(): void
    {
        $provider = $this->makeProvider();

        $this->withDeviceToken($provider->user);

        $push = $this->mockPushService();

        $push->shouldReceive('sendToUser')
            ->once()
            ->withArgs(function (User $user, $notification) use ($provider) {
                return $user->is($provider->user)
                    && $notification instanceof CadastroAprovadoPush;
            });

        $approved = $this->service->approve($provider->id);

        $this->assertSame(ApprovalStatusEnum::ACCEPTED->value, $approved->approval_status->value);
    }

    public function test_update_para_accepted_dispara_push(): void
    {
        $provider = $this->makeProvider();

        $this->withDeviceToken($provider->user);

        $push = $this->mockPushService();

        $push->shouldReceive('sendToUser')->once();

        $this->service->update($provider->id, [
            'approval_status' => ApprovalStatusEnum::ACCEPTED->value,
        ]);
    }

    public function test_update_que_nao_muda_o_status_nao_dispara_push(): void
    {
        $provider = $this->makeProvider();

        $this->withDeviceToken($provider->user);

        $push = $this->mockPushService();

        $push->shouldNotReceive('sendToUser');

        $this->service->update($provider->id, ['birth_date' => '1991-02-02']);
    }

    public function test_aprovar_prestador_ja_aprovado_nao_reenvia_push(): void
    {
        $provider = $this->makeProvider(ApprovalStatusEnum::ACCEPTED);

        $this->withDeviceToken($provider->user);

        $push = $this->mockPushService();

        $push->shouldNotReceive('sendToUser');

        $this->service->approve($provider->id);
    }

    /**
     * Rede de seguranca extra: mesmo que a transicao seja detectada de novo
     * (reprovar e reaprovar, por exemplo), o log impede o envio duplicado.
     */
    public function test_push_ja_registrada_no_log_nao_e_reenviada(): void
    {
        $provider = $this->makeProvider();

        $this->withDeviceToken($provider->user);

        PushNotificationLog::query()->create([
            'label'    => (new CadastroAprovadoPush)->label(),
            'user_id'  => $provider->user->id,
            'target'   => PushNotificationTargetEnum::PRESTADOR->value,
            'category' => PushNotificationCategoryEnum::TRANSACIONAL->value,
            'sent_at'  => now()->subDay(),
        ]);

        $push = $this->mockPushService();

        $push->shouldNotReceive('sendToUser');

        $this->service->approve($provider->id);
    }

    public function test_prestador_sem_device_token_ativo_nao_dispara_push(): void
    {
        $provider = $this->makeProvider();

        $this->withDeviceToken($provider->user, active: false);

        $push = $this->mockPushService();

        $push->shouldNotReceive('sendToUser');

        $approved = $this->service->approve($provider->id);

        $this->assertSame(ApprovalStatusEnum::ACCEPTED->value, $approved->approval_status->value);
    }

    public function test_token_de_outro_app_nao_dispara_push_do_prestador(): void
    {
        $provider = $this->makeProvider();

        $this->withDeviceToken($provider->user, appType: PushNotificationTargetEnum::CLIENTE);

        $push = $this->mockPushService();

        $push->shouldNotReceive('sendToUser');

        $this->service->approve($provider->id);
    }

    public function test_falha_no_envio_da_push_nao_impede_a_aprovacao(): void
    {
        $provider = $this->makeProvider();

        $this->withDeviceToken($provider->user);

        $push = $this->mockPushService();

        $push->shouldReceive('sendToUser')
            ->once()
            ->andThrow(new \RuntimeException('FCM indisponivel'));

        $approved = $this->service->approve($provider->id);

        $this->assertSame(ApprovalStatusEnum::ACCEPTED->value, $approved->approval_status->value);
    }

    //

    private function mockPushService(): MockInterface
    {
        return $this->mock(PushNotificationService::class);
    }

    private function makeProvider(?ApprovalStatusEnum $status = null): Provider
    {
        $user = User::query()->create([
            'name'     => 'Prestador Teste',
            'email'    => 'prestador'.uniqid().'@teste.com',
            'password' => 'secret',
            'type'     => UserTypeEnum::PROVIDER->value,
        ]);

        $provider = Provider::query()->create([
            'user_id'         => $user->id,
            'document'        => '06767310905',
            'birth_date'      => '1990-01-01',
            'approval_status' => ($status ?? ApprovalStatusEnum::PENDING)->value,
        ]);

        return $provider->load('user');
    }

    private function withDeviceToken(
        User $user,
        bool $active = true,
        ?PushNotificationTargetEnum $appType = null,
    ): DeviceToken {
        return DeviceToken::query()->create([
            'user_id'  => $user->id,
            'token'    => 'tok_'.uniqid(),
            'platform' => 'android',
            'app_type' => ($appType ?? PushNotificationTargetEnum::PRESTADOR)->value,
            'active'   => $active,
        ]);
    }
}
