<?php

namespace Tests\Feature\Jobs;

use App\Enums\AccountStatusEnum;
use App\Enums\NotificationTypeEnum;
use App\Jobs\CheckPlatformCredentialsJob;
use App\Models\Notification;
use App\Models\PlatformAccount;
use App\Modules\Lemmy\Services\LemmyApiService;
use App\Services\Notification\NotificationService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;
use Tests\TestCase;

class CheckPlatformCredentialsJobTest extends TestCase
{
    use RefreshDatabase;

    private function runJob(bool $loginSucceeds): void
    {
        $api = Mockery::mock(LemmyApiService::class);
        $api->shouldReceive('login')->andReturn($loginSucceeds ? 'a-token' : null);

        $job = new class($api) extends CheckPlatformCredentialsJob
        {
            public function __construct(private LemmyApiService $api) {}

            protected function makeApiService(PlatformAccount $account): LemmyApiService
            {
                return $this->api;
            }
        };

        $job->handle(app(NotificationService::class));
    }

    /**
     * @param  array<string, mixed>  $attributes
     */
    private function account(array $attributes = []): PlatformAccount
    {
        return PlatformAccount::factory()->create(array_merge(['is_active' => true], $attributes));
    }

    public function test_a_successful_login_marks_the_account_healthy(): void
    {
        $account = $this->account(['status' => AccountStatusEnum::UNTESTED]);

        $this->runJob(true);

        $account->refresh();
        $this->assertSame(AccountStatusEnum::HEALTHY, $account->status);
        $this->assertSame(0, $account->consecutive_failures);
        $this->assertNotNull($account->last_tested_at);
    }

    public function test_a_successful_login_clears_earlier_failures(): void
    {
        $account = $this->account(['status' => AccountStatusEnum::UNHEALTHY, 'consecutive_failures' => 2]);

        $this->runJob(true);

        $account->refresh();
        $this->assertSame(AccountStatusEnum::HEALTHY, $account->status);
        $this->assertSame(0, $account->consecutive_failures);
    }

    public function test_a_single_failure_does_not_mark_the_account_unhealthy(): void
    {
        $account = $this->account(['status' => AccountStatusEnum::HEALTHY]);

        $this->runJob(false);

        $account->refresh();
        $this->assertSame(AccountStatusEnum::HEALTHY, $account->status);
        $this->assertSame(1, $account->consecutive_failures);
    }

    public function test_the_threshold_failure_marks_the_account_unhealthy(): void
    {
        $account = $this->account([
            'status' => AccountStatusEnum::HEALTHY,
            'consecutive_failures' => PlatformAccount::FAILURES_BEFORE_UNHEALTHY - 1,
        ]);

        $this->runJob(false);

        $this->assertSame(AccountStatusEnum::UNHEALTHY, $account->refresh()->status);
    }

    public function test_a_single_failure_does_not_notify(): void
    {
        $this->account(['status' => AccountStatusEnum::HEALTHY]);

        $this->runJob(false);

        $this->assertDatabaseCount('notifications', 0);
    }

    public function test_becoming_unhealthy_notifies(): void
    {
        $account = $this->account([
            'status' => AccountStatusEnum::HEALTHY,
            'consecutive_failures' => PlatformAccount::FAILURES_BEFORE_UNHEALTHY - 1,
            'username' => 'newsbot',
        ]);

        $this->runJob(false);

        $this->assertDatabaseHas('notifications', [
            'type' => NotificationTypeEnum::CREDENTIAL_EXPIRED->value,
            'notifiable_type' => $account->getMorphClass(),
            'notifiable_id' => $account->id,
        ]);
        $this->assertStringContainsString('newsbot', Notification::first()->title);
    }

    public function test_an_account_already_unhealthy_does_not_notify_again(): void
    {
        $this->account([
            'status' => AccountStatusEnum::UNHEALTHY,
            'consecutive_failures' => PlatformAccount::FAILURES_BEFORE_UNHEALTHY,
        ]);

        $this->runJob(false);

        $this->assertDatabaseCount('notifications', 0);
    }

    public function test_inactive_accounts_are_not_checked(): void
    {
        $account = $this->account(['is_active' => false, 'status' => AccountStatusEnum::UNTESTED]);

        $this->runJob(false);

        $account->refresh();
        $this->assertSame(AccountStatusEnum::UNTESTED, $account->status);
        $this->assertNull($account->last_tested_at);
    }

    public function test_an_exception_during_login_counts_as_a_failure(): void
    {
        $account = $this->account(['status' => AccountStatusEnum::HEALTHY]);

        $api = Mockery::mock(LemmyApiService::class);
        $api->shouldReceive('login')->andThrow(new \RuntimeException('instance unreachable'));

        $job = new class($api) extends CheckPlatformCredentialsJob
        {
            public function __construct(private LemmyApiService $api) {}

            protected function makeApiService(PlatformAccount $account): LemmyApiService
            {
                return $this->api;
            }
        };

        $job->handle(app(NotificationService::class));

        $this->assertSame(1, $account->refresh()->consecutive_failures);
    }
}
