<?php

namespace Tests\Unit\Dashboard\Stats;

use App\Dashboard\Stats\ArticlesTrend;
use App\Dashboard\Stats\DailySeriesStat;
use App\Dashboard\Stats\Series;
use App\Dashboard\Stats\SeriesResult;
use App\Enums\PublishStatusEnum;
use App\Models\Article;
use App\Models\ArticlePublication;
use App\Models\PlatformChannel;
use App\Models\RouteArticle;
use App\Support\DateRange;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;

class ArticlesTrendTest extends TestCase
{
    use RefreshDatabase;

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

        Carbon::setTestNow(Carbon::parse('2026-07-15 12:00:00'));
    }

    protected function tearDown(): void
    {
        Carbon::setTestNow();

        parent::tearDown();
    }

    private function range(): DateRange
    {
        return new DateRange(
            Carbon::parse('2026-07-01 00:00:00'),
            Carbon::parse('2026-07-31 23:59:59'),
        );
    }

    /**
     * @return array<string, Series>
     */
    private function seriesByName(SeriesResult $result): array
    {
        $out = [];

        foreach ($result->series as $one) {
            $out[$one->name] = $one;
        }

        return $out;
    }

    public function test_it_counts_articles_fetched_per_day(): void
    {
        Article::factory()->count(2)->create([
            'created_at' => Carbon::parse('2026-07-10 09:00:00'),
        ]);
        Article::factory()->create([
            'created_at' => Carbon::parse('2026-07-11 09:00:00'),
        ]);

        $result = (new ArticlesTrend)->for($this->range());

        $days = $result->labels;
        $fetched = $this->seriesByName($result)['Fetched'];

        $this->assertSame(2, $fetched->values[array_search('2026-07-10', $days, true)]);
        $this->assertSame(1, $fetched->values[array_search('2026-07-11', $days, true)]);
    }

    public function test_it_zero_fills_days_with_no_articles(): void
    {
        Article::factory()->create([
            'created_at' => Carbon::parse('2026-07-10 09:00:00'),
        ]);
        Article::factory()->create([
            'created_at' => Carbon::parse('2026-07-12 09:00:00'),
        ]);

        $result = (new ArticlesTrend)->for($this->range());

        $days = $result->labels;
        $fetched = $this->seriesByName($result)['Fetched'];

        $this->assertSame(0, $fetched->values[array_search('2026-07-11', $days, true)]);
        $this->assertSame(1, $fetched->values[array_search('2026-07-10', $days, true)]);
        $this->assertSame(1, $fetched->values[array_search('2026-07-12', $days, true)]);
    }

    public function test_it_excludes_articles_outside_the_range(): void
    {
        Article::factory()->create([
            'created_at' => Carbon::parse('2026-07-01 00:00:00'),
        ]);
        Article::factory()->create([
            'created_at' => Carbon::parse('2026-07-31 23:59:59'),
        ]);
        Article::factory()->create([
            'created_at' => Carbon::parse('2026-06-30 23:59:59'),
        ]);
        Article::factory()->create([
            'created_at' => Carbon::parse('2026-08-01 00:00:00'),
        ]);

        $result = (new ArticlesTrend)->for($this->range());

        $fetched = $this->seriesByName($result)['Fetched'];

        $this->assertSame(2, array_sum($fetched->values));
    }

    public function test_it_counts_publications_per_day_from_article_publications(): void
    {
        $article = Article::factory()->create([
            'created_at' => Carbon::parse('2026-01-01 00:00:00'),
        ]);

        ArticlePublication::factory()->create([
            'article_id' => $article->id,
            'published_at' => Carbon::parse('2026-07-10 09:00:00'),
        ]);
        ArticlePublication::factory()->count(2)->create([
            'article_id' => $article->id,
            'published_at' => Carbon::parse('2026-07-11 09:00:00'),
        ]);

        $result = (new ArticlesTrend)->for($this->range());

        $days = $result->labels;
        $published = $this->seriesByName($result)['Published'];

        $this->assertSame(1, $published->values[array_search('2026-07-10', $days, true)]);
        $this->assertSame(2, $published->values[array_search('2026-07-11', $days, true)]);
    }

    public function test_it_does_not_source_the_published_series_from_route_article_publish_status(): void
    {
        $article = Article::factory()->create([
            'created_at' => Carbon::parse('2026-07-10 09:00:00'),
        ]);

        RouteArticle::factory()->create([
            'article_id' => $article->id,
            'publish_status' => PublishStatusEnum::PUBLISHED,
            'created_at' => Carbon::parse('2026-07-10 09:00:00'),
        ]);

        $result = (new ArticlesTrend)->for($this->range());

        $published = $this->seriesByName($result)['Published'];

        $this->assertSame(0, array_sum($published->values));
    }

    public function test_it_counts_each_channel_publication_of_one_article_separately(): void
    {
        $article = Article::factory()->create([
            'created_at' => Carbon::parse('2026-01-01 00:00:00'),
        ]);

        $channels = PlatformChannel::factory()->count(3)->create();

        foreach ($channels as $channel) {
            ArticlePublication::factory()->create([
                'article_id' => $article->id,
                'platform_channel_id' => $channel->id,
                'published_at' => Carbon::parse('2026-07-10 09:00:00'),
            ]);
        }

        $result = (new ArticlesTrend)->for($this->range());

        $published = $this->seriesByName($result)['Published'];

        $this->assertSame(3, array_sum($published->values));
    }

    public function test_it_returns_both_series_over_one_shared_label_axis(): void
    {
        $result = (new ArticlesTrend)->for($this->range());

        $this->assertCount(count($this->range()->days()), $result->labels);
        $this->assertCount(2, $result->series);

        $names = array_map(fn ($series) => $series->name, $result->series);
        $this->assertSame(['Fetched', 'Published'], $names);

        foreach ($result->series as $series) {
            $this->assertCount(count($result->labels), $series->values);
        }
    }

    public function test_it_splits_days_at_midnight(): void
    {
        Article::factory()->create([
            'created_at' => Carbon::parse('2026-07-10 23:59:59'),
        ]);
        Article::factory()->create([
            'created_at' => Carbon::parse('2026-07-11 00:00:01'),
        ]);

        $publishedArticle = Article::factory()->create([
            'created_at' => Carbon::parse('2026-01-01 00:00:00'),
        ]);

        ArticlePublication::factory()->create([
            'article_id' => $publishedArticle->id,
            'published_at' => Carbon::parse('2026-07-10 23:59:59'),
        ]);
        ArticlePublication::factory()->create([
            'article_id' => $publishedArticle->id,
            'published_at' => Carbon::parse('2026-07-11 00:00:01'),
        ]);

        $result = (new ArticlesTrend)->for($this->range());

        $days = $result->labels;
        $fetched = $this->seriesByName($result)['Fetched'];
        $published = $this->seriesByName($result)['Published'];

        $this->assertSame(1, $fetched->values[array_search('2026-07-10', $days, true)]);
        $this->assertSame(1, $fetched->values[array_search('2026-07-11', $days, true)]);
        $this->assertSame(2, array_sum($fetched->values));

        $this->assertSame(1, $published->values[array_search('2026-07-10', $days, true)]);
        $this->assertSame(1, $published->values[array_search('2026-07-11', $days, true)]);
        $this->assertSame(2, array_sum($published->values));
    }

    public function test_it_is_a_daily_series_stat_and_reports_too_wide_without_querying(): void
    {
        $stat = new ArticlesTrend;

        $this->assertInstanceOf(DailySeriesStat::class, $stat);

        Article::factory()->create([
            'created_at' => Carbon::now(),
        ]);

        $queries = 0;
        DB::listen(function () use (&$queries) {
            $queries++;
        });

        $result = $stat->for(DateRange::preset('all'));

        $this->assertTrue($result->isTooWide());
        $this->assertSame(0, $queries);
    }
}
