<?php

namespace Tests\Feature;

use App\Actions\FetchArticleDataAction;
use App\Actions\FetchFeedArticlesAction;
use App\Enums\ApprovalStatusEnum;
use App\Enums\LogLevelEnum;
use App\Events\ActionPerformed;
use App\Events\ExceptionLogged;
use App\Events\ExceptionOccurred;
use App\Events\NewArticleFetched;
use App\Events\RouteArticleApproved;
use App\Jobs\ArticleDiscoveryForFeedJob;
use App\Jobs\ArticleDiscoveryJob;
use App\Jobs\PublishNextArticleJob;
use App\Jobs\SyncChannelPostsJob;
use App\Listeners\LogExceptionToDatabase;
use App\Listeners\ValidateArticleListener;
use App\Models\Article;
use App\Models\Feed;
use App\Models\Keyword;
use App\Models\Log;
use App\Models\PlatformChannel;
use App\Models\Route;
use App\Models\RouteArticle;
use App\Models\Setting;
use App\Services\Log\LogSaver;
use App\Services\Notification\NotificationService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;

class JobsAndEventsTest extends TestCase
{
    use RefreshDatabase;

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

    public function test_article_discovery_job_processes_successfully(): void
    {
        Queue::fake();

        $feed = Feed::factory()->create(['is_active' => true]);

        $logSaver = app(LogSaver::class);
        $job = new ArticleDiscoveryJob;
        $job->handle($logSaver);

        // Should dispatch individual feed jobs
        Queue::assertPushed(ArticleDiscoveryForFeedJob::class);
    }

    public function test_article_discovery_for_feed_job_processes_feed(): void
    {
        Event::fake();

        $feed = Feed::factory()->create([
            'url' => 'https://example.com/feed',
            'is_active' => true,
        ]);

        // Mock the feed fetch in the container
        $mockFetcher = \Mockery::mock(FetchFeedArticlesAction::class);
        $article1 = Article::factory()->create(['url' => 'https://example.com/article1', 'feed_id' => $feed->id]);
        $article2 = Article::factory()->create(['url' => 'https://example.com/article2', 'feed_id' => $feed->id]);
        $mockFetcher->shouldReceive('execute')
            ->with($feed)
            ->andReturn(collect([$article1, $article2]));

        $this->app->instance(FetchFeedArticlesAction::class, $mockFetcher);

        $logSaver = app(LogSaver::class);
        $articleFetcher = app(FetchFeedArticlesAction::class);
        $job = new ArticleDiscoveryForFeedJob($feed);
        $job->handle($logSaver, $articleFetcher, app(NotificationService::class));

        // Should have articles in database (existing articles created by factory)
        $this->assertCount(2, Article::all());
        // Note: Events are not fired by ArticleDiscoveryForFeedJob directly
        // They would be fired by the Article model when created
    }

    public function test_sync_channel_posts_job_processes_successfully(): void
    {
        $channel = PlatformChannel::factory()->create();
        $job = new SyncChannelPostsJob($channel);

        // Test that job can be constructed and has correct properties
        $this->assertEquals('sync', $job->queue);
        $this->assertInstanceOf(SyncChannelPostsJob::class, $job);

        // Don't actually run the job to avoid HTTP calls
        $this->assertTrue(true);
    }

    public function test_publish_next_article_job_has_correct_configuration(): void
    {
        $job = new PublishNextArticleJob;

        $this->assertEquals('publishing', $job->queue);
        $this->assertInstanceOf(PublishNextArticleJob::class, $job);
    }

    public function test_new_article_fetched_event_is_dispatched(): void
    {
        Event::fake();

        $feed = Feed::factory()->create();
        $article = Article::factory()->create(['feed_id' => $feed->id]);

        event(new NewArticleFetched($article));

        Event::assertDispatched(NewArticleFetched::class, function (NewArticleFetched $event) use ($article) {
            return $event->article->id === $article->id;
        });
    }

    public function test_exception_occurred_event_is_dispatched(): void
    {
        Event::fake();

        $exception = new \Exception('Test exception');

        event(new ExceptionOccurred($exception, LogLevelEnum::ERROR, 'Test exception', ['context' => 'test']));

        Event::assertDispatched(ExceptionOccurred::class, function (ExceptionOccurred $event) {
            return $event->exception->getMessage() === 'Test exception';
        });
    }

    public function test_exception_logged_event_is_dispatched(): void
    {
        Event::fake();

        $log = Log::factory()->create([
            'level' => 'error',
            'message' => 'Test error',
            'context' => json_encode(['key' => 'value']),
        ]);

        event(new ExceptionLogged($log));

        Event::assertDispatched(ExceptionLogged::class, function (ExceptionLogged $event) {
            return $event->log->message === 'Test error';
        });
    }

    public function test_validate_article_listener_processes_new_article(): void
    {
        // Disable approvals so listener auto-approves valid articles
        Setting::setBool('enable_publishing_approvals', false);

        $feed = Feed::factory()->create();
        /** @var Route $route */
        $route = Route::factory()->active()->create(['feed_id' => $feed->id]);
        Keyword::factory()->active()->create([
            'feed_id' => $feed->id,
            'platform_channel_id' => $route->platform_channel_id,
            'keyword' => 'Belgium',
        ]);

        $article = Article::factory()->create([
            'feed_id' => $feed->id,
        ]);

        // Mock the article-data fetch to return valid data
        $mockFetcher = \Mockery::mock(FetchArticleDataAction::class);
        $this->app->instance(FetchArticleDataAction::class, $mockFetcher);
        $mockFetcher->shouldReceive('execute')
            ->with($article)
            ->andReturn([
                'title' => 'Belgian News',
                'description' => 'News from Belgium',
                'full_article' => 'This is a test article about Belgium and Belgian politics.',
            ]);

        $listener = app(ValidateArticleListener::class);
        $event = new NewArticleFetched($article);

        $listener->handle($event);

        $article->refresh();
        $this->assertNotNull($article->validated_at);

        $routeArticle = RouteArticle::where('article_id', $article->id)->first();
        $this->assertNotNull($routeArticle);
        $this->assertEquals(ApprovalStatusEnum::APPROVED, $routeArticle->approval_status);
    }

    public function test_log_exception_to_database_listener_creates_log(): void
    {
        $log = Log::factory()->create([
            'level' => 'error',
            'message' => 'Test exception message',
            'context' => json_encode(['error' => 'details']),
        ]);

        $listener = new LogExceptionToDatabase;
        $exception = new \Exception('Test exception message');
        $event = new ExceptionOccurred($exception, LogLevelEnum::ERROR, 'Test exception message');

        $listener->handle($event);

        $this->assertDatabaseHas('logs', [
            'level' => 'error',
            'message' => 'Test exception message',
        ]);

        $savedLog = Log::where('message', 'Test exception message')->first();
        $this->assertNotNull($savedLog);
        $this->assertEquals(LogLevelEnum::ERROR, $savedLog->level);
    }

    public function test_event_listener_registration_works(): void
    {
        // Test that events are properly bound to listeners
        $listeners = Event::getListeners(ActionPerformed::class);
        $this->assertNotEmpty($listeners);

        $listeners = Event::getListeners(NewArticleFetched::class);
        $this->assertNotEmpty($listeners);

        $listeners = Event::getListeners(RouteArticleApproved::class);
        $this->assertNotEmpty($listeners);

        $listeners = Event::getListeners(ExceptionOccurred::class);
        $this->assertNotEmpty($listeners);
    }

    public function test_job_retry_configuration(): void
    {
        $job = new PublishNextArticleJob;

        // Test that job has unique configuration
        $this->assertObjectHasProperty('uniqueFor', $job);
        $this->assertEquals(300, $job->uniqueFor);
    }

    public function test_job_queue_configuration(): void
    {
        $feed = Feed::factory()->create(['url' => 'https://unique-test-feed.com/rss']);
        $channel = PlatformChannel::factory()->create();
        $article = Article::factory()->create(['feed_id' => $feed->id]);

        $discoveryJob = new ArticleDiscoveryJob;
        $feedJob = new ArticleDiscoveryForFeedJob($feed);
        $publishJob = new PublishNextArticleJob;
        $syncJob = new SyncChannelPostsJob($channel);

        // Test queue assignments
        $this->assertEquals('feed-discovery', $discoveryJob->queue ?? 'default');
        $this->assertEquals('feed-discovery', $feedJob->queue ?? 'discovery');
        $this->assertEquals('publishing', $publishJob->queue);
        $this->assertEquals('sync', $syncJob->queue ?? 'sync');
    }

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