<?php

namespace Tests\Unit\Jobs;

use App\Actions\FetchFeedArticlesAction;
use App\Jobs\ArticleDiscoveryForFeedJob;
use App\Models\Article;
use App\Models\Feed;
use App\Services\Log\LogSaver;
use App\Services\Notification\NotificationService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Mockery;
use Tests\TestCase;

class ArticleDiscoveryForFeedJobTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        Queue::fake();
    }

    public function test_constructor_sets_correct_queue(): void
    {
        $feed = Feed::factory()->make();
        $job = new ArticleDiscoveryForFeedJob($feed);

        $this->assertEquals('feed-discovery', $job->queue);
    }

    public function test_job_implements_should_queue(): void
    {
        $feed = Feed::factory()->make();
        $job = new ArticleDiscoveryForFeedJob($feed);

        $this->assertInstanceOf(ShouldQueue::class, $job);
    }

    public function test_job_uses_queueable_trait(): void
    {
        $feed = Feed::factory()->make();
        $job = new ArticleDiscoveryForFeedJob($feed);

        $this->assertContains(
            Queueable::class,
            class_uses($job)
        );
    }

    public function test_handle_fetches_articles_and_updates_feed(): void
    {
        // Arrange
        $feed = Feed::factory()->create([
            'name' => 'Test Feed',
            'url' => 'https://example.com/feed',
            'last_fetched_at' => null,
        ]);

        $mockArticles = collect(['article1', 'article2']);

        // Mock the feed fetch
        $articleFetcherMock = Mockery::mock(FetchFeedArticlesAction::class);
        $articleFetcherMock->shouldReceive('execute')
            ->once()
            ->with($feed)
            ->andReturn($mockArticles);

        // Mock LogSaver
        $logSaverMock = Mockery::mock(LogSaver::class);
        $logSaverMock->shouldReceive('info')
            ->with('Starting feed article fetch', null, [
                'feed_id' => $feed->id,
                'feed_name' => $feed->name,
                'feed_url' => $feed->url,
            ])
            ->once();

        $logSaverMock->shouldReceive('info')
            ->with('Feed article fetch completed', null, [
                'feed_id' => $feed->id,
                'feed_name' => $feed->name,
                'articles_count' => 2,
            ])
            ->once();

        $job = new ArticleDiscoveryForFeedJob($feed);

        // Act
        $job->handle($logSaverMock, $articleFetcherMock, app(NotificationService::class));

        // Assert
        $feed->refresh();
        $this->assertNotNull($feed->last_fetched_at);
        $this->assertTrue($feed->last_fetched_at->greaterThan(now()->subMinute()));
    }

    public function test_dispatch_for_all_active_feeds_dispatches_jobs_with_delay(): void
    {
        // Arrange
        $feeds = Feed::factory()->count(3)->create(['is_active' => true]);
        Feed::factory()->create(['is_active' => false]); // inactive feed should be ignored

        // Mock LogSaver
        $logSaverMock = Mockery::mock(LogSaver::class);
        $logSaverMock->shouldReceive('info')
            ->times(3) // Once for each active feed
            ->with('Dispatched feed discovery job', null, Mockery::type('array'));

        $this->app->instance(LogSaver::class, $logSaverMock);

        // Act
        ArticleDiscoveryForFeedJob::dispatchForAllActiveFeeds();

        // Assert
        Queue::assertPushed(ArticleDiscoveryForFeedJob::class, 3);

        // Verify jobs were dispatched (cannot access private $feed property in test)
    }

    public function test_dispatch_for_all_active_feeds_applies_correct_delays(): void
    {
        // Arrange
        Feed::factory()->count(2)->create(['is_active' => true]);

        // Mock LogSaver
        $logSaverMock = Mockery::mock(LogSaver::class);
        $logSaverMock->shouldReceive('info')->times(2);

        $this->app->instance(LogSaver::class, $logSaverMock);

        // Act
        ArticleDiscoveryForFeedJob::dispatchForAllActiveFeeds();

        // Assert
        Queue::assertPushed(ArticleDiscoveryForFeedJob::class, 2);

        // Verify jobs are pushed with delays
        Queue::assertPushed(ArticleDiscoveryForFeedJob::class, function ($job) {
            return $job->delay !== null;
        });
    }

    public function test_dispatch_for_all_active_feeds_with_no_active_feeds(): void
    {
        // Arrange
        Feed::factory()->count(2)->create(['is_active' => false]);

        // Act
        ArticleDiscoveryForFeedJob::dispatchForAllActiveFeeds();

        // Assert
        Queue::assertNothingPushed();
    }

    public function test_feed_discovery_delay_constant_exists(): void
    {
        $reflection = new \ReflectionClass(ArticleDiscoveryForFeedJob::class);
        $constant = $reflection->getConstant('FEED_DISCOVERY_DELAY_MINUTES');

        $this->assertEquals(5, $constant);
    }

    public function test_job_can_be_serialized(): void
    {
        $feed = Feed::factory()->create(['name' => 'Test Feed']);
        $job = new ArticleDiscoveryForFeedJob($feed);

        $serialized = serialize($job);
        $unserialized = unserialize($serialized);

        $this->assertInstanceOf(ArticleDiscoveryForFeedJob::class, $unserialized);
        $this->assertEquals($job->queue, $unserialized->queue);
        // Note: Cannot test feed property directly as it's private
        // but serialization/unserialization working proves the job structure is intact
    }

    public function test_handle_logs_start_message_with_correct_context(): void
    {
        // Arrange
        $feed = Feed::factory()->create([
            'name' => 'Test Feed',
            'url' => 'https://example.com/feed',
        ]);

        $mockArticles = collect([new Article]);

        // Mock the feed fetch
        $articleFetcherMock = Mockery::mock(FetchFeedArticlesAction::class);
        $articleFetcherMock->shouldReceive('execute')
            ->once()
            ->andReturn($mockArticles);

        // Mock LogSaver with specific expectations
        $logSaverMock = Mockery::mock(LogSaver::class);
        $logSaverMock->shouldReceive('info')
            ->with('Starting feed article fetch', null, [
                'feed_id' => $feed->id,
                'feed_name' => 'Test Feed',
                'feed_url' => 'https://example.com/feed',
            ])
            ->once();

        $logSaverMock->shouldReceive('info')
            ->with('Feed article fetch completed', null, Mockery::type('array'))
            ->once();

        $job = new ArticleDiscoveryForFeedJob($feed);

        // Act
        $job->handle($logSaverMock, $articleFetcherMock, app(NotificationService::class));

        // Assert - Mockery expectations are verified in tearDown
        $this->assertTrue(true);
    }

    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }
}
