<?php

namespace Tests\Feature\Listeners;

use App\Actions\FetchArticleDataAction;
use App\Actions\PublishRouteArticleAction;
use App\Enums\NotificationSeverityEnum;
use App\Enums\NotificationTypeEnum;
use App\Events\RouteArticleApproved;
use App\Listeners\PublishApprovedArticleListener;
use App\Models\Article;
use App\Models\ArticlePublication;
use App\Models\Feed;
use App\Models\Notification;
use App\Models\Route;
use App\Models\RouteArticle;
use App\Services\Notification\NotificationService;
use App\Services\Publishing\ArticlePublishingService;
use App\Services\Publishing\PublishOutcome;
use Exception;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;
use Tests\TestCase;

class PublishApprovedArticleListenerTest extends TestCase
{
    use RefreshDatabase;

    private function createApprovedRouteArticle(string $title = 'Test Article'): RouteArticle
    {
        $feed = Feed::factory()->create();
        /** @var Route $route */
        $route = Route::factory()->active()->create(['feed_id' => $feed->id]);
        $article = Article::factory()->unvalidated()->create([
            'feed_id' => $feed->id,
            'title' => $title,
        ]);

        /** @var RouteArticle $routeArticle */
        $routeArticle = RouteArticle::factory()->forRoute($route)->approved()->create([
            'article_id' => $article->id,
        ]);

        return $routeArticle;
    }

    public function test_exception_during_publishing_creates_error_notification(): void
    {
        $routeArticle = $this->createApprovedRouteArticle();

        $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class);
        $articleFetcherMock->shouldReceive('execute')
            ->once()
            ->andThrow(new Exception('Connection refused'));

        $publishingServiceMock = Mockery::mock(ArticlePublishingService::class);

        $listener = new PublishApprovedArticleListener(new PublishRouteArticleAction($articleFetcherMock, $publishingServiceMock, new NotificationService));
        $listener->handle(new RouteArticleApproved($routeArticle));

        $this->assertDatabaseHas('notifications', [
            'type' => NotificationTypeEnum::PUBLISH_FAILED->value,
            'severity' => NotificationSeverityEnum::ERROR->value,
            'notifiable_type' => $routeArticle->article->getMorphClass(),
            'notifiable_id' => $routeArticle->article_id,
        ]);

        $notification = Notification::first();
        $this->assertStringContainsString('Test Article', $notification->title);
        $this->assertStringContainsString('Connection refused', $notification->message);
    }

    public function test_no_publication_created_creates_warning_notification(): void
    {
        $routeArticle = $this->createApprovedRouteArticle();

        $extractedData = ['title' => 'Test Article', 'description' => 'Test description'];

        $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class);
        $articleFetcherMock->shouldReceive('execute')
            ->once()
            ->andReturn($extractedData);

        $publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
        $publishingServiceMock->shouldReceive('publishRouteArticle')
            ->once()
            ->andReturn(PublishOutcome::failure('No publication created'));

        $listener = new PublishApprovedArticleListener(new PublishRouteArticleAction($articleFetcherMock, $publishingServiceMock, new NotificationService));
        $listener->handle(new RouteArticleApproved($routeArticle));

        $this->assertDatabaseHas('notifications', [
            'type' => NotificationTypeEnum::PUBLISH_FAILED->value,
            'severity' => NotificationSeverityEnum::WARNING->value,
            'notifiable_type' => $routeArticle->article->getMorphClass(),
            'notifiable_id' => $routeArticle->article_id,
        ]);

        $notification = Notification::first();
        $this->assertStringContainsString('Test Article', $notification->title);
    }

    public function test_successful_publish_does_not_create_notification(): void
    {
        $routeArticle = $this->createApprovedRouteArticle();

        $extractedData = ['title' => 'Test Article', 'description' => 'Test description'];

        $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class);
        $articleFetcherMock->shouldReceive('execute')
            ->once()
            ->andReturn($extractedData);

        $publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
        $publishingServiceMock->shouldReceive('publishRouteArticle')
            ->once()
            ->andReturn(PublishOutcome::published($this->makePublication()));

        $listener = new PublishApprovedArticleListener(new PublishRouteArticleAction($articleFetcherMock, $publishingServiceMock, new NotificationService));
        $listener->handle(new RouteArticleApproved($routeArticle));

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

    public function test_skips_already_published_to_channel(): void
    {
        $routeArticle = $this->createApprovedRouteArticle();

        ArticlePublication::factory()->create([
            'article_id' => $routeArticle->article_id,
            'platform_channel_id' => $routeArticle->platform_channel_id,
        ]);

        $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class);
        $articleFetcherMock->shouldNotReceive('execute');

        $publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
        $publishingServiceMock->shouldNotReceive('publishRouteArticle');

        $listener = new PublishApprovedArticleListener(new PublishRouteArticleAction($articleFetcherMock, $publishingServiceMock, new NotificationService));
        $listener->handle(new RouteArticleApproved($routeArticle));

        $this->assertTrue(true);
    }

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

    private function makePublication(): ArticlePublication
    {
        /** @var ArticlePublication $publication */
        $publication = ArticlePublication::factory()->make();

        return $publication;
    }
}
