<?php

namespace Tests\Feature\Jobs;

use App\Actions\FetchFeedArticlesAction;
use App\Enums\ActivityTypeEnum;
use App\Enums\NotificationSeverityEnum;
use App\Enums\NotificationTypeEnum;
use App\Jobs\ArticleDiscoveryForFeedJob;
use App\Models\ActivityLog;
use App\Models\Article;
use App\Models\Feed;
use App\Models\Notification;
use App\Services\Log\LogSaver;
use App\Services\Notification\NotificationService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Collection;
use Mockery;
use Tests\TestCase;

class ArticleDiscoveryForFeedJobEmptyFetchTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @param  Collection<int, Article>|null  $articles
     */
    private function runJobForFeed(Feed $feed, ?Collection $articles = null): void
    {
        $fetcher = Mockery::mock(FetchFeedArticlesAction::class);
        $fetcher->shouldReceive('execute')
            ->andReturn($articles ?? collect());

        (new ArticleDiscoveryForFeedJob($feed))->handle(
            app(LogSaver::class),
            $fetcher,
            app(NotificationService::class)
        );
    }

    public function test_empty_fetch_creates_notification(): void
    {
        $feed = Feed::factory()->create(['is_active' => true]);

        $this->runJobForFeed($feed);

        $this->assertDatabaseHas('notifications', [
            'type' => NotificationTypeEnum::FEED_EMPTY->value,
            'severity' => NotificationSeverityEnum::WARNING->value,
            'notifiable_type' => $feed->getMorphClass(),
            'notifiable_id' => $feed->id,
        ]);
    }

    public function test_fetch_with_articles_does_not_create_notification(): void
    {
        $feed = Feed::factory()->create(['is_active' => true]);
        $articles = Article::factory()->count(2)->create(['feed_id' => $feed->id]);

        $this->runJobForFeed($feed, $articles);

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

    public function test_notification_message_names_the_feed_url(): void
    {
        $feed = Feed::factory()->create([
            'is_active' => true,
            'name' => 'Belga',
            'url' => 'https://example.test/retired-feed.xml',
        ]);

        $this->runJobForFeed($feed);

        $notification = Notification::first();

        $this->assertStringContainsString('Belga', $notification->title);
        $this->assertStringContainsString('https://example.test/retired-feed.xml', $notification->message);
    }

    public function test_empty_fetch_still_updates_last_fetched_at(): void
    {
        $feed = Feed::factory()->create(['is_active' => true, 'last_fetched_at' => null]);

        $this->runJobForFeed($feed);

        $this->assertNotNull($feed->fresh()->last_fetched_at);
    }

    public function test_does_not_create_duplicate_notification_when_unread_exists(): void
    {
        $feed = Feed::factory()->create(['is_active' => true]);

        Notification::factory()
            ->type(NotificationTypeEnum::FEED_EMPTY)
            ->unread()
            ->create([
                'notifiable_type' => $feed->getMorphClass(),
                'notifiable_id' => $feed->id,
            ]);

        $this->runJobForFeed($feed);

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

    public function test_creates_new_notification_when_previous_is_read(): void
    {
        $feed = Feed::factory()->create(['is_active' => true]);

        Notification::factory()
            ->type(NotificationTypeEnum::FEED_EMPTY)
            ->read()
            ->create([
                'notifiable_type' => $feed->getMorphClass(),
                'notifiable_id' => $feed->id,
            ]);

        $this->runJobForFeed($feed);

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

    public function test_empty_fetch_records_error_activity_not_fetch(): void
    {
        $feed = Feed::factory()->create(['is_active' => true, 'name' => 'Belga']);

        $this->runJobForFeed($feed);

        $entry = ActivityLog::first();

        $this->assertSame(ActivityTypeEnum::ERROR, $entry->type);
        $this->assertStringContainsString('Belga', $entry->message);
        $this->assertSame(0, ActivityLog::ofType(ActivityTypeEnum::FETCH)->count());
    }

    public function test_fetch_with_articles_records_fetch_activity(): void
    {
        $feed = Feed::factory()->create(['is_active' => true, 'name' => 'VRT']);
        $articles = Article::factory()->count(2)->create(['feed_id' => $feed->id]);

        $this->runJobForFeed($feed, $articles);

        $entry = ActivityLog::first();

        $this->assertSame(ActivityTypeEnum::FETCH, $entry->type);
        $this->assertStringContainsString('2 articles', $entry->message);
        $this->assertSame($feed->getMorphClass(), $entry->subject_type);
    }

    public function test_notification_for_one_feed_does_not_suppress_another(): void
    {
        $notified = Feed::factory()->create(['is_active' => true]);
        $other = Feed::factory()->create(['is_active' => true]);

        Notification::factory()
            ->type(NotificationTypeEnum::FEED_EMPTY)
            ->unread()
            ->create([
                'notifiable_type' => $notified->getMorphClass(),
                'notifiable_id' => $notified->id,
            ]);

        $this->runJobForFeed($other);

        $this->assertDatabaseHas('notifications', [
            'type' => NotificationTypeEnum::FEED_EMPTY->value,
            'notifiable_id' => $other->id,
        ]);
        $this->assertDatabaseCount('notifications', 2);
    }
}
