<?php

namespace Tests\Feature;

use App\Enums\ActivityTypeEnum;
use App\Enums\ApprovalStatusEnum;
use App\Events\ActivityLogged;
use App\Events\RouteArticleApproved;
use App\Livewire\Articles;
use App\Models\ActivityLog;
use App\Models\Article;
use App\Models\Feed;
use App\Models\Route;
use App\Models\RouteArticle;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Livewire\Livewire;
use Tests\TestCase;

class ActivityRecordingTest extends TestCase
{
    use RefreshDatabase;

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

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

        return $routeArticle;
    }

    public function test_approving_records_activity(): void
    {
        Event::fake([RouteArticleApproved::class]);

        $this->pendingRouteArticle()->approve();

        $log = ActivityLog::ofType(ActivityTypeEnum::APPROVE)->first();

        $this->assertNotNull($log);
        $this->assertStringContainsString('A Headline', $log->message);
        $this->assertSame(Article::class, $log->subject_type);
    }

    public function test_rejecting_records_activity(): void
    {
        $this->pendingRouteArticle()->reject();

        $log = ActivityLog::ofType(ActivityTypeEnum::REJECT)->first();

        $this->assertNotNull($log);
        $this->assertStringContainsString('A Headline', $log->message);
    }

    public function test_approving_an_already_approved_article_records_nothing(): void
    {
        Event::fake([RouteArticleApproved::class]);

        $routeArticle = $this->pendingRouteArticle();
        $routeArticle->approve();
        $routeArticle->approve();

        $this->assertSame(1, ActivityLog::ofType(ActivityTypeEnum::APPROVE)->count());
    }

    public function test_rejecting_an_already_rejected_article_records_nothing(): void
    {
        $routeArticle = $this->pendingRouteArticle();
        $routeArticle->reject();
        $routeArticle->reject();

        $this->assertSame(1, ActivityLog::ofType(ActivityTypeEnum::REJECT)->count());
    }

    public function test_clearing_pending_articles_records_one_summary_entry(): void
    {
        $routeArticle = $this->pendingRouteArticle();
        $this->pendingRouteArticle();

        Livewire::test(Articles::class)->call('clear');

        $entries = ActivityLog::ofType(ActivityTypeEnum::REJECT)->get();

        $this->assertCount(1, $entries);
        $this->assertStringContainsString('2 pending articles', $entries->first()->message);
        $this->assertSame(ApprovalStatusEnum::REJECTED, $routeArticle->fresh()->approval_status);
    }

    public function test_clearing_nothing_records_no_entry(): void
    {
        Livewire::test(Articles::class)->call('clear');

        $this->assertSame(0, ActivityLog::count());
    }

    public function test_one_event_records_exactly_one_entry(): void
    {
        ActivityLogged::dispatch(ActivityTypeEnum::FETCH, 'Single entry');

        $this->assertSame(1, ActivityLog::where('message', 'Single entry')->count());
    }

    public function test_a_long_message_is_truncated_to_fit_the_column(): void
    {
        ActivityLogged::dispatch(ActivityTypeEnum::PUBLISH, str_repeat('a', 400));

        $this->assertSame(255, strlen(ActivityLog::first()->message));
    }

    public function test_an_empty_context_is_stored_as_null(): void
    {
        ActivityLogged::dispatch(ActivityTypeEnum::FETCH, 'No context');

        $this->assertNull(ActivityLog::first()->context);
    }
}
