<?php

namespace Tests\Feature;

use App\Actions\FetchArticleDataAction;
use App\Actions\PublishRouteArticleAction;
use App\Enums\NotificationTypeEnum;
use App\Enums\PublishStatusEnum;
use App\Events\RouteArticleApproved;
use App\Listeners\PublishApprovedArticleListener;
use App\Models\Article;
use App\Models\Feed;
use App\Models\PlatformAccount;
use App\Models\PlatformChannel;
use App\Models\PlatformChannelPost;
use App\Models\PlatformInstance;
use App\Models\Route;
use App\Models\RouteArticle;
use App\Modules\Lemmy\Services\LemmyApiService;
use App\Modules\Lemmy\Services\LemmyPublisher;
use App\Services\Log\LogSaver;
use App\Services\Notification\NotificationService;
use App\Services\Publishing\ArticlePublishingService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Mockery;
use Tests\TestCase;

class MirrorDuplicateDetectionTest extends TestCase
{
    use RefreshDatabase;

    /** @var array{RouteArticle, PlatformChannel, Article} */
    private array $fixture;

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

        $feed = Feed::factory()->create();
        $instance = PlatformInstance::factory()->create(['url' => 'https://lemmy.test']);
        $channel = PlatformChannel::factory()->create([
            'platform_instance_id' => $instance->id,
            'channel_id' => 'news',
            'name' => 'news',
        ]);
        $account = PlatformAccount::factory()->create(['instance_url' => 'https://lemmy.test']);

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

        $channel->platformAccounts()->attach($account->id, ['is_active' => true, 'priority' => 50]);

        $article = Article::factory()->create([
            'feed_id' => $feed->id,
            'url' => 'https://news.test/already-posted',
            'title' => 'Already Posted',
        ]);

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

        $this->fixture = [$routeArticle, $channel, $article];
    }

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

    private function service(?LemmyPublisher $publisher = null): ArticlePublishingService
    {
        $service = Mockery::mock(ArticlePublishingService::class, [app(LogSaver::class)])->makePartial();
        $service->shouldAllowMockingProtectedMethods();
        $service->shouldReceive('makePublisher')->andReturn(
            $publisher ?? Mockery::mock(LemmyPublisher::class)
        );

        return $service;
    }

    public function test_sync_writes_the_key_the_duplicate_check_reads(): void
    {
        [$routeArticle, $channel, $article] = $this->fixture;

        // Populate the mirror through the real sync path rather than seeding a
        // row by hand, so write and read cannot silently disagree.
        Http::fake([
            '*/api/v3/post/list*' => Http::response([
                'posts' => [[
                    'post' => [
                        'id' => 555,
                        'url' => $article->url,
                        'name' => 'Already Posted',
                        'published' => '2026-08-02T10:00:00Z',
                    ],
                ]],
            ]),
        ]);

        (new LemmyApiService('https://lemmy.test'))
            ->syncChannelPosts('token', $channel, 8);

        $this->assertDatabaseCount('platform_channel_posts', 1);

        $publisher = Mockery::mock(LemmyPublisher::class);
        $publisher->shouldNotReceive('publishToChannel');

        $result = $this->service($publisher)->publishRouteArticle($routeArticle, ['title' => 'Already Posted']);

        $this->assertTrue($result->wasSkipped());
        $this->assertDatabaseCount('article_publications', 0);
    }

    public function test_a_skipped_duplicate_is_not_reported_as_a_publish_failure(): void
    {
        [$routeArticle, $channel, $article] = $this->fixture;

        PlatformChannelPost::storePost($channel, '555', $article->url, 'Already Posted');

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

        $publisher = Mockery::mock(LemmyPublisher::class);
        $publisher->shouldNotReceive('publishToChannel');

        (new PublishApprovedArticleListener(new PublishRouteArticleAction($fetcher, $this->service($publisher), new NotificationService)))
            ->handle(new RouteArticleApproved($routeArticle));

        $this->assertSame(PublishStatusEnum::SKIPPED, $routeArticle->fresh()->publish_status);
        $this->assertDatabaseCount('notifications', 0);
    }

    public function test_a_genuine_failure_is_still_reported(): void
    {
        [$routeArticle] = $this->fixture;

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

        $publisher = Mockery::mock(LemmyPublisher::class);
        $publisher->shouldReceive('publishToChannel')->andThrow(new \RuntimeException('Lemmy rejected the post'));

        (new PublishApprovedArticleListener(new PublishRouteArticleAction($fetcher, $this->service($publisher), new NotificationService)))
            ->handle(new RouteArticleApproved($routeArticle));

        $this->assertSame(PublishStatusEnum::ERROR, $routeArticle->fresh()->publish_status);
        $this->assertDatabaseHas('notifications', [
            'type' => NotificationTypeEnum::PUBLISH_FAILED->value,
        ]);
    }

    public function test_mirror_is_keyed_by_the_local_channel(): void
    {
        [, $channel, $article] = $this->fixture;

        PlatformChannelPost::storePost(
            $channel,
            '555',
            $article->url,
            'Already Posted',
        );

        $this->assertDatabaseHas('platform_channel_posts', [
            'platform_channel_id' => $channel->id,
            'post_id' => '555',
        ]);
    }

    public function test_a_second_instance_with_the_same_community_name_does_not_collide(): void
    {
        [, $channel, $article] = $this->fixture;

        $otherInstance = PlatformInstance::factory()->create(['url' => 'https://other.test']);
        $otherChannel = PlatformChannel::factory()->create([
            'platform_instance_id' => $otherInstance->id,
            'channel_id' => 'news',
            'name' => 'news',
        ]);

        PlatformChannelPost::storePost($channel, '1', $article->url, 'Same Title');

        // Same community name on a different instance is a different community.
        $this->assertFalse(
            PlatformChannelPost::duplicateExists($otherChannel, $article->url, 'Same Title')
        );

        $this->assertTrue(
            PlatformChannelPost::duplicateExists($channel, $article->url, 'Same Title')
        );
    }
}
