<?php

namespace Tests\Feature\Livewire;

use App\Enums\ApprovalStatusEnum;
use App\Events\RouteArticleApproved;
use App\Livewire\Articles;
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\Features\SupportTesting\Testable;
use Livewire\Livewire;
use Tests\TestCase;

class ArticlesTest extends TestCase
{
    use RefreshDatabase;

    private function createRouteArticle(ApprovalStatusEnum $status = ApprovalStatusEnum::PENDING, string $title = 'Test Article'): 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' => $title]);

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

        return $routeArticle;
    }

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

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

        return $routeArticle;
    }

    /**
     * @param  Testable<Articles>  $component
     * @return array<int, int>
     */
    private function renderedFeedGroupIds(Testable $component): array
    {
        preg_match_all('/wire:key="feed-group-(\d+)"/', $component->html(), $matches);

        return array_map('intval', $matches[1]);
    }

    public function test_renders_successfully(): void
    {
        Livewire::test(Articles::class)
            ->assertStatus(200);
    }

    public function test_defaults_to_pending_tab(): void
    {
        Livewire::test(Articles::class)
            ->assertSet('tab', 'pending');
    }

    public function test_pending_tab_shows_only_pending_route_articles(): void
    {
        $feed = Feed::factory()->create(['name' => 'VRT News']);

        $this->createRouteArticleForFeed($feed, 'Pending Article');
        $this->createRouteArticleForFeed($feed, 'Approved Article', ApprovalStatusEnum::APPROVED);
        $this->createRouteArticleForFeed($feed, 'Rejected Article', ApprovalStatusEnum::REJECTED);

        Livewire::test(Articles::class)
            ->call('toggleFeed', $feed->id)
            ->assertSee('Pending Article')
            ->assertDontSee('Approved Article')
            ->assertDontSee('Rejected Article');
    }

    public function test_all_tab_shows_all_route_articles(): void
    {
        $this->createRouteArticle(ApprovalStatusEnum::PENDING, 'Pending Article');
        $this->createRouteArticle(ApprovalStatusEnum::APPROVED, 'Approved Article');
        $this->createRouteArticle(ApprovalStatusEnum::REJECTED, 'Rejected Article');

        Livewire::test(Articles::class)
            ->call('setTab', 'all')
            ->assertSee('Pending Article')
            ->assertSee('Approved Article')
            ->assertSee('Rejected Article');
    }

    public function test_all_tab_search_filters_by_title(): void
    {
        $this->createRouteArticle(ApprovalStatusEnum::PENDING, 'Belgian Politics Update');
        $this->createRouteArticle(ApprovalStatusEnum::PENDING, 'Weather Forecast Today');

        Livewire::test(Articles::class)
            ->call('setTab', 'all')
            ->set('search', 'Belgian')
            ->assertSee('Belgian Politics Update')
            ->assertDontSee('Weather Forecast Today');
    }

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

        $routeArticle = $this->createRouteArticle(ApprovalStatusEnum::PENDING);

        Livewire::test(Articles::class)
            ->call('approve', $routeArticle->id);

        $this->assertEquals(ApprovalStatusEnum::APPROVED, $routeArticle->fresh()->approval_status);

        Event::assertDispatched(RouteArticleApproved::class);
    }

    public function test_reject_changes_status(): void
    {
        $routeArticle = $this->createRouteArticle(ApprovalStatusEnum::PENDING);

        Livewire::test(Articles::class)
            ->call('reject', $routeArticle->id);

        $this->assertEquals(ApprovalStatusEnum::REJECTED, $routeArticle->fresh()->approval_status);
    }

    public function test_restore_changes_status_back_to_pending(): void
    {
        $routeArticle = $this->createRouteArticle(ApprovalStatusEnum::REJECTED);

        Livewire::test(Articles::class)
            ->call('restore', $routeArticle->id);

        $this->assertEquals(ApprovalStatusEnum::PENDING, $routeArticle->fresh()->approval_status);
    }

    public function test_clear_rejects_all_pending_route_articles(): void
    {
        $pending1 = $this->createRouteArticle(ApprovalStatusEnum::PENDING);
        $pending2 = $this->createRouteArticle(ApprovalStatusEnum::PENDING);
        $approved = $this->createRouteArticle(ApprovalStatusEnum::APPROVED);

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

        $this->assertEquals(ApprovalStatusEnum::REJECTED, $pending1->fresh()->approval_status);
        $this->assertEquals(ApprovalStatusEnum::REJECTED, $pending2->fresh()->approval_status);
        $this->assertEquals(ApprovalStatusEnum::APPROVED, $approved->fresh()->approval_status);
    }

    public function test_clear_only_rejects_the_filtered_feed(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $vrtArticle = $this->createRouteArticleForFeed($vrt, 'VRT Headline');
        $belgaArticle = $this->createRouteArticleForFeed($belga, 'Belga Headline');

        Livewire::test(Articles::class)
            ->set('feedId', $vrt->id)
            ->call('clear');

        $this->assertEquals(ApprovalStatusEnum::REJECTED, $vrtArticle->fresh()->approval_status);
        $this->assertEquals(ApprovalStatusEnum::PENDING, $belgaArticle->fresh()->approval_status);
    }

    public function test_clear_is_a_no_op_when_the_filtered_feed_has_nothing_pending(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $vrtArticle = $this->createRouteArticleForFeed($vrt, 'VRT Headline');
        $belgaApproved = $this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED);

        Livewire::test(Articles::class)
            ->set('feedId', $belga->id)
            ->call('clear');

        $this->assertEquals(ApprovalStatusEnum::PENDING, $vrtArticle->fresh()->approval_status);
        $this->assertEquals(ApprovalStatusEnum::APPROVED, $belgaApproved->fresh()->approval_status);
    }

    public function test_clear_confirmation_counts_only_the_filtered_feed(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $this->createRouteArticleForFeed($vrt, 'VRT One');
        $this->createRouteArticleForFeed($vrt, 'VRT Two');
        $this->createRouteArticleForFeed($belga, 'Belga One');

        Livewire::test(Articles::class)
            ->set('feedId', $vrt->id)
            ->assertSee('Reject 2 pending route articles from the selected feed?')
            ->set('feedId', null)
            ->assertSee('Reject 3 pending route articles?');
    }

    public function test_clear_button_hidden_when_filtered_feed_has_nothing_pending(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');
        $this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED);

        Livewire::test(Articles::class)
            ->set('feedId', $belga->id)
            ->assertDontSee('Clear Feed')
            ->assertDontSee('Clear All');
    }

    public function test_clear_button_labels_its_scope(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');

        Livewire::test(Articles::class)
            ->assertSee('Clear All')
            ->set('feedId', $vrt->id)
            ->assertSee('Clear Feed');
    }

    public function test_pending_count_badge_shows_correct_count(): void
    {
        $this->createRouteArticle(ApprovalStatusEnum::PENDING);
        $this->createRouteArticle(ApprovalStatusEnum::PENDING);
        $this->createRouteArticle(ApprovalStatusEnum::APPROVED);

        Livewire::test(Articles::class)
            ->assertSeeInOrder(['Pending', '2']);
    }

    public function test_switching_tabs_resets_search(): void
    {
        Livewire::test(Articles::class)
            ->call('setTab', 'all')
            ->set('search', 'something')
            ->call('setTab', 'pending')
            ->assertSet('search', '')
            ->assertSet('tab', 'pending');
    }

    public function test_shows_route_name_in_listing(): void
    {
        $feed = Feed::factory()->create(['name' => 'VRT News']);
        /** @var Route $route */
        $route = Route::factory()->active()->create(['feed_id' => $feed->id]);
        $article = Article::factory()->create(['feed_id' => $feed->id, 'title' => 'Test']);

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

        Livewire::test(Articles::class)
            ->assertSee('VRT News');
    }

    public function test_route_eyebrow_renders_above_the_headline(): void
    {
        $feed = Feed::factory()->create(['name' => 'VRT News']);

        $this->createRouteArticleForFeed($feed, 'The Headline');

        Livewire::test(Articles::class)
            ->call('toggleFeed', $feed->id)
            ->assertSeeInOrder(['Routed from VRT News to', 'The Headline']);
    }

    public function test_route_eyebrow_shows_the_feed_colour_dot(): void
    {
        $feed = Feed::factory()->create(['name' => 'VRT News']);

        $this->createRouteArticleForFeed($feed, 'The Headline');

        Livewire::test(Articles::class)
            ->call('toggleFeed', $feed->id)
            ->assertSeeHtml($feed->displayColor()->dotClass());
    }

    public function test_pending_tab_lists_feeds_collapsed_without_articles(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');
        $this->createRouteArticleForFeed($belga, 'Belga Headline');

        Livewire::test(Articles::class)
            ->assertSee('VRT News')
            ->assertSee('Belga Press')
            ->assertDontSee('VRT Headline')
            ->assertDontSee('Belga Headline');
    }

    public function test_pending_feed_badge_shows_total_count_not_page_count(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);

        for ($i = 0; $i < 20; $i++) {
            $this->createRouteArticleForFeed($vrt, "VRT Article {$i}");
        }

        $html = (string) preg_replace('/\s+/', ' ', Livewire::test(Articles::class)->html());

        $this->assertMatchesRegularExpression('#VRT News</span> <span class="ml-auto[^"]*"> 20 </span>#', $html);
    }

    public function test_expanding_a_feed_reveals_only_that_feeds_articles(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');
        $this->createRouteArticleForFeed($belga, 'Belga Headline');

        Livewire::test(Articles::class)
            ->call('toggleFeed', $vrt->id)
            ->assertSee('VRT Headline')
            ->assertDontSee('Belga Headline');
    }

    public function test_expanding_a_feed_shows_all_its_articles_beyond_one_page(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);

        $base = now()->startOfSecond();

        for ($i = 0; $i < 20; $i++) {
            $this->travelTo($base->copy()->addMinutes($i), function () use ($vrt, $i) {
                $this->createRouteArticleForFeed($vrt, "VRT Article {$i}");
            });
        }

        $this->travelBack();

        $component = Livewire::test(Articles::class)->call('toggleFeed', $vrt->id);

        for ($i = 0; $i < 20; $i++) {
            $component->assertSee("VRT Article {$i}");
        }
    }

    public function test_toggling_an_expanded_feed_collapses_it(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');

        Livewire::test(Articles::class)
            ->call('toggleFeed', $vrt->id)
            ->assertSee('VRT Headline')
            ->call('toggleFeed', $vrt->id)
            ->assertDontSee('VRT Headline');
    }

    public function test_switching_tabs_collapses_expanded_feeds(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');

        Livewire::test(Articles::class)
            ->call('toggleFeed', $vrt->id)
            ->call('setTab', 'all')
            ->call('setTab', 'pending')
            ->assertSet('expandedFeeds', [])
            ->assertDontSee('VRT Headline');
    }

    public function test_all_tab_shows_a_flat_list_not_feed_groups(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $this->createRouteArticleForFeed($vrt, 'VRT Article');
        $this->createRouteArticleForFeed($belga, 'Belga Article');

        Livewire::test(Articles::class)
            ->call('setTab', 'all')
            ->assertViewHas('pendingFeeds', null)
            ->assertSee('VRT Article')
            ->assertSee('Belga Article');
    }

    public function test_feed_filter_narrows_pending_tab_to_one_feed(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');
        $this->createRouteArticleForFeed($belga, 'Belga Headline');

        $component = Livewire::test(Articles::class)->set('feedId', $vrt->id);

        $this->assertSame([$vrt->id], $this->renderedFeedGroupIds($component));
    }

    public function test_feed_filter_narrows_all_tab_to_one_feed(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');
        $this->createRouteArticleForFeed($belga, 'Belga Headline');

        Livewire::test(Articles::class)
            ->call('setTab', 'all')
            ->set('feedId', $vrt->id)
            ->assertSee('VRT Headline')
            ->assertDontSee('Belga Headline');
    }

    public function test_feed_filter_combines_with_search_on_all_tab(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $this->createRouteArticleForFeed($vrt, 'Belgian Politics Update');
        $this->createRouteArticleForFeed($vrt, 'Weather Forecast Today');
        $this->createRouteArticleForFeed($belga, 'Belgian Economy Report');

        Livewire::test(Articles::class)
            ->call('setTab', 'all')
            ->set('feedId', $vrt->id)
            ->set('search', 'Belgian')
            ->assertSee('Belgian Politics Update')
            ->assertDontSee('Weather Forecast Today')
            ->assertDontSee('Belgian Economy Report');
    }

    public function test_feed_filter_combines_with_tab_status_filtering(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);

        $this->createRouteArticleForFeed($vrt, 'VRT Pending');
        $this->createRouteArticleForFeed($vrt, 'VRT Approved', ApprovalStatusEnum::APPROVED);

        Livewire::test(Articles::class)
            ->set('feedId', $vrt->id)
            ->call('toggleFeed', $vrt->id)
            ->assertSee('VRT Pending')
            ->assertDontSee('VRT Approved');
    }

    public function test_clearing_feed_filter_restores_all_feeds(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');
        $this->createRouteArticleForFeed($belga, 'Belga Headline');

        $component = Livewire::test(Articles::class)->set('feedId', $vrt->id);

        $this->assertSame([$vrt->id], $this->renderedFeedGroupIds($component));

        $component->set('feedId', null);

        $this->assertEqualsCanonicalizing([$vrt->id, $belga->id], $this->renderedFeedGroupIds($component));
    }

    public function test_changing_feed_filter_resets_pagination(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);

        for ($i = 0; $i < 20; $i++) {
            $this->createRouteArticleForFeed($vrt, "VRT Article {$i}");
        }

        Livewire::test(Articles::class)
            ->call('setTab', 'all')
            ->set('paginators.page', 2)
            ->set('feedId', $vrt->id)
            ->assertSet('paginators.page', 1);
    }

    public function test_changing_feed_filter_collapses_expanded_feeds(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');

        Livewire::test(Articles::class)
            ->call('toggleFeed', $vrt->id)
            ->assertSee('VRT Headline')
            ->set('feedId', $vrt->id)
            ->assertSet('expandedFeeds', [])
            ->assertDontSee('VRT Headline');
    }

    public function test_feed_filter_persists_across_tab_switches(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');

        Livewire::test(Articles::class)
            ->set('feedId', $vrt->id)
            ->call('setTab', 'all')
            ->assertSet('feedId', $vrt->id)
            ->call('setTab', 'pending')
            ->assertSet('feedId', $vrt->id);
    }

    public function test_feed_filter_options_only_include_feeds_with_route_articles(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $unused = Feed::factory()->create(['name' => 'Unused Feed']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');

        Livewire::test(Articles::class)
            ->assertSeeHtml('<option value="'.$vrt->id.'">VRT News</option>')
            ->assertDontSeeHtml('<option value="'.$unused->id.'">Unused Feed</option>');
    }

    public function test_empty_state_when_feed_filter_matches_nothing_on_pending_tab(): void
    {
        $vrt = Feed::factory()->create(['name' => 'VRT News']);
        $belga = Feed::factory()->create(['name' => 'Belga Press']);

        $this->createRouteArticleForFeed($vrt, 'VRT Headline');
        $this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED);

        Livewire::test(Articles::class)
            ->set('feedId', $belga->id)
            ->assertSee('No pending articles for the selected feed.');
    }

    public function test_empty_state_on_pending_tab(): void
    {
        Livewire::test(Articles::class)
            ->assertSee('No pending articles');
    }

    public function test_empty_state_on_all_tab(): void
    {
        Livewire::test(Articles::class)
            ->call('setTab', 'all')
            ->assertSee('No route articles have been created yet.');
    }

    private function failedRouteArticle(string $reason = 'couldnt_find_community'): RouteArticle
    {
        /** @var RouteArticle $routeArticle */
        $routeArticle = RouteArticle::factory()->approved()->create();
        $routeArticle->recordPublishFailed($reason);

        return $routeArticle;
    }

    public function test_the_failed_tab_lists_only_failed_articles(): void
    {
        $failed = $this->failedRouteArticle();
        /** @var RouteArticle $ok */
        $ok = RouteArticle::factory()->approved()->create();

        Livewire::test(Articles::class)
            ->call('setTab', 'failed')
            ->assertSee($failed->article->title)
            ->assertDontSee($ok->article->title);
    }

    public function test_the_failed_tab_shows_the_failure_reason(): void
    {
        $this->failedRouteArticle('couldnt_find_community');

        Livewire::test(Articles::class)
            ->call('setTab', 'failed')
            ->assertSee('couldnt_find_community');
    }

    public function test_the_failed_tab_badge_counts_failed_articles(): void
    {
        $this->failedRouteArticle();
        $this->failedRouteArticle();
        RouteArticle::factory()->approved()->create();

        Livewire::test(Articles::class)
            ->assertViewHas('failedCount', 2);
    }

    public function test_the_failed_tab_is_empty_when_nothing_failed(): void
    {
        RouteArticle::factory()->approved()->create();

        Livewire::test(Articles::class)
            ->call('setTab', 'failed')
            ->assertSee('Nothing has failed to publish.')
            ->assertViewHas('failedCount', 0);
    }

    public function test_retrying_clears_the_failure_and_requeues_the_article(): void
    {
        $routeArticle = $this->failedRouteArticle();

        Livewire::test(Articles::class)
            ->call('setTab', 'failed')
            ->call('retryPublish', $routeArticle->id);

        $routeArticle->refresh();

        $this->assertNull($routeArticle->publish_error);
        $this->assertTrue(
            RouteArticle::query()->dueForPublishing()->whereKey($routeArticle->getKey())->exists()
        );
    }

    public function test_retrying_keeps_the_approval_decision_intact(): void
    {
        $routeArticle = $this->failedRouteArticle();
        $decidedAt = $routeArticle->decided_at;

        Livewire::test(Articles::class)->call('retryPublish', $routeArticle->id);

        $routeArticle->refresh();

        $this->assertTrue($routeArticle->isApproved());
        $this->assertEquals($decidedAt, $routeArticle->decided_at);
    }

    public function test_retrying_an_article_that_did_not_fail_does_nothing(): void
    {
        /** @var RouteArticle $routeArticle */
        $routeArticle = RouteArticle::factory()->approved()->create();

        Livewire::test(Articles::class)->call('retryPublish', $routeArticle->id);

        $this->assertNull($routeArticle->fresh()->publish_error);
    }
}
