<?php

declare(strict_types=1);

use App\Models\User;
use Inertia\Testing\AssertableInertia;
use Illuminate\Support\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use cPad\Plugins\News\Models\NewsArticle;
use cPad\Plugins\News\Models\NewsCategory;
use cPad\Plugins\News\Models\NewsTag;
use App\Models\Artwork;

uses(RefreshDatabase::class);

function studioNewsCategory(array $attributes = []): NewsCategory
{
    return NewsCategory::query()->create(array_merge([
        'name' => 'Studio News',
        'slug' => 'studio-news',
        'description' => 'Studio News category',
        'position' => 0,
        'is_active' => true,
    ], $attributes));
}

function studioNewsTag(array $attributes = []): NewsTag
{
    return NewsTag::query()->create(array_merge([
        'name' => 'Studio',
        'slug' => 'studio',
    ], $attributes));
}

it('forbids newsroom studio pages for non moderators', function (): void {
    $user = User::factory()->create();

    $this->actingAs($user)
        ->get(route('studio.news.index'))
        ->assertForbidden();
});

it('renders newsroom studio pages for moderators', function (): void {
    Carbon::setTestNow(Carbon::parse('2026-06-04 13:45:00'));

    $moderator = User::factory()->create([
        'role' => 'moderator',
        'username' => 'modnews',
        'name' => 'Moderator News',
    ]);
    $author = User::factory()->create([
        'username' => 'writernews',
        'name' => 'Writer News',
    ]);
    $category = studioNewsCategory();
    $tag = studioNewsTag();
    $article = NewsArticle::query()->create([
        'title' => 'Moderated newsroom article',
        'slug' => 'moderated-newsroom-article',
        'excerpt' => 'Studio-managed newsroom article.',
        'content' => 'Studio body',
        'author_id' => $author->id,
        'category_id' => $category->id,
        'type' => NewsArticle::TYPE_EDITORIAL,
        'status' => 'published',
        'editorial_status' => NewsArticle::EDITORIAL_STATUS_PUBLISHED,
        'published_at' => Carbon::parse('2026-04-05 09:30:00'),
    ]);
    $article->tags()->sync([$tag->id]);

    $this->actingAs($moderator)
        ->get(route('studio.news.index'))
        ->assertOk()
        ->assertInertia(fn (AssertableInertia $page) => $page
            ->component('Studio/StudioNewsIndex')
            ->where('title', 'Newsroom')
            ->where('listing.items.0.title', 'Moderated newsroom article')
            ->where('createUrl', route('studio.news.create')));

    $this->actingAs($moderator)
        ->get(route('studio.news.create'))
        ->assertOk()
        ->assertInertia(fn (AssertableInertia $page) => $page
            ->component('Studio/StudioNewsEditor')
            ->where('title', 'Create article')
            ->has('typeOptions')
            ->has('statusOptions')
            ->has('categoryOptions')
            ->has('tagOptions')
            ->where('defaultAuthor.id', $moderator->id)
            ->where('defaultAuthor.title', 'Moderator News')
            ->where('defaultPublishedAt', '2026-06-04T13:45'));

    $this->actingAs($moderator)
        ->get(route('studio.news.categories'))
        ->assertOk()
        ->assertInertia(fn (AssertableInertia $page) => $page
            ->component('Studio/StudioNewsTaxonomies')
            ->where('activeTab', 'categories')
            ->has('categories.0')
            ->has('tags.0'));

    $this->actingAs($moderator)
        ->get(route('studio.news.preview', ['article' => $article->id]))
        ->assertOk()
        ->assertSee('Preview mode')
        ->assertSee('Moderated newsroom article');

    Carbon::setTestNow();
});

it('decodes legacy apostrophe entities in the newsroom editor', function (): void {
    $moderator = User::factory()->create([
        'role' => 'moderator',
        'username' => 'modedit',
        'name' => 'Moderator Edit',
    ]);
    $author = User::factory()->create([
        'username' => 'writeredit',
        'name' => 'Writer Edit',
    ]);
    $category = studioNewsCategory();

    $article = NewsArticle::query()->create([
        'title' => 'Ericsson ENGINE to expand Telia&acute;s international carrier network',
        'slug' => 'ericsson-engine-to-expand-telias-international-carrier-network',
        'excerpt' => 'Studio-managed newsroom article.',
        'content' => 'Studio body',
        'author_id' => $author->id,
        'category_id' => $category->id,
        'type' => NewsArticle::TYPE_EDITORIAL,
        'status' => 'draft',
        'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
    ]);

    $this->actingAs($moderator)
        ->get(route('studio.news.edit', ['article' => $article->id]))
        ->assertOk()
        ->assertInertia(fn (AssertableInertia $page) => $page
            ->component('Studio/StudioNewsEditor')
            ->where('article.title', "Ericsson ENGINE to expand Telia's international carrier network"));
});

it('filters newsroom listing by status type and category', function (): void {
    $moderator = User::factory()->create([
        'role' => 'moderator',
    ]);
    $category = studioNewsCategory([
        'name' => 'Filtered Category',
        'slug' => 'filtered-category',
    ]);
    $otherCategory = studioNewsCategory([
        'name' => 'Other Category',
        'slug' => 'other-category',
    ]);
    $author = User::factory()->create();

    NewsArticle::query()->create([
        'title' => 'Keep Me',
        'slug' => 'keep-me',
        'excerpt' => 'Should survive filtering.',
        'content' => 'Content',
        'author_id' => $author->id,
        'category_id' => $category->id,
        'type' => NewsArticle::TYPE_ANNOUNCEMENT,
        'status' => 'draft',
        'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
    ]);

    NewsArticle::query()->create([
        'title' => 'Drop Me By Type',
        'slug' => 'drop-me-type',
        'excerpt' => 'Wrong type.',
        'content' => 'Content',
        'author_id' => $author->id,
        'category_id' => $category->id,
        'type' => NewsArticle::TYPE_EDITORIAL,
        'status' => 'draft',
        'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
    ]);

    NewsArticle::query()->create([
        'title' => 'Drop Me By Category',
        'slug' => 'drop-me-category',
        'excerpt' => 'Wrong category.',
        'content' => 'Content',
        'author_id' => $author->id,
        'category_id' => $otherCategory->id,
        'type' => NewsArticle::TYPE_ANNOUNCEMENT,
        'status' => 'draft',
        'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
    ]);

    $this->actingAs($moderator)
        ->get(route('studio.news.index', [
            'status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
            'type' => NewsArticle::TYPE_ANNOUNCEMENT,
            'category_id' => $category->id,
        ]))
        ->assertOk()
        ->assertInertia(fn (AssertableInertia $page) => $page
            ->component('Studio/StudioNewsIndex')
            ->where('listing.filters.status', NewsArticle::EDITORIAL_STATUS_DRAFT)
            ->where('listing.filters.type', NewsArticle::TYPE_ANNOUNCEMENT)
            ->where('listing.filters.category_id', $category->id)
            ->has('listing.items', 1)
            ->where('listing.items.0.title', 'Keep Me'));
});

it('paginates newsroom listing', function (): void {
    $moderator = User::factory()->create([
        'role' => 'moderator',
    ]);
    $author = User::factory()->create();

    foreach (range(1, 16) as $index) {
        NewsArticle::query()->create([
            'title' => "Paged Article {$index}",
            'slug' => "paged-article-{$index}",
            'excerpt' => 'Paginated newsroom article.',
            'content' => 'Content',
            'author_id' => $author->id,
            'type' => NewsArticle::TYPE_ANNOUNCEMENT,
            'status' => 'draft',
            'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
        ]);
    }

    $this->actingAs($moderator)
        ->get(route('studio.news.index'))
        ->assertOk()
        ->assertInertia(fn (AssertableInertia $page) => $page
            ->component('Studio/StudioNewsIndex')
            ->where('listing.meta.current_page', 1)
            ->where('listing.meta.last_page', 2)
            ->where('listing.meta.total', 16)
            ->has('listing.items', 15));

    $this->actingAs($moderator)
        ->get(route('studio.news.index', ['page' => 2]))
        ->assertOk()
        ->assertInertia(fn (AssertableInertia $page) => $page
            ->component('Studio/StudioNewsIndex')
            ->where('listing.meta.current_page', 2)
            ->where('listing.meta.last_page', 2)
            ->where('listing.meta.total', 16)
            ->has('listing.items', 1));
});

it('stores a newsroom draft with taxonomy links', function (): void {
    $moderator = User::factory()->create([
        'role' => 'moderator',
        'username' => 'editornews',
        'name' => 'Editor News',
    ]);
    $author = User::factory()->create();
    $category = studioNewsCategory([
        'name' => 'Launches',
        'slug' => 'launches',
    ]);
    $tag = studioNewsTag([
        'name' => 'Update',
        'slug' => 'update',
    ]);

    $response = $this->actingAs($moderator)->post(route('studio.news.store'), [
        'title' => 'Stored newsroom draft',
        'slug' => 'stored-newsroom-draft',
        'excerpt' => 'Stored through the Studio newsroom form.',
        'content' => 'This article was created through the new Studio News flow.',
        'type' => NewsArticle::TYPE_ANNOUNCEMENT,
        'category_id' => $category->id,
        'author_id' => $author->id,
        'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
        'published_at' => null,
        'tag_ids' => [$tag->id],
        'new_tag_names' => ['Studio Exclusive'],
        'is_featured' => true,
        'is_pinned' => false,
        'meta_title' => 'Stored newsroom draft meta',
        'meta_description' => 'Stored newsroom draft description',
    ]);

    $article = NewsArticle::query()->where('slug', 'stored-newsroom-draft')->firstOrFail();

    $response->assertRedirect(route('studio.news.edit', ['article' => $article->id]));

    $this->assertDatabaseHas('news_articles', [
        'id' => $article->id,
        'title' => 'Stored newsroom draft',
        'slug' => 'stored-newsroom-draft',
        'author_id' => $author->id,
        'category_id' => $category->id,
        'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
        'status' => 'draft',
    ]);

    expect($article->canonical_url)->toBe(route('news.show', ['slug' => 'stored-newsroom-draft']));

    expect($article->tags()->pluck('news_tags.name')->all())
        ->toContain('Update')
        ->toContain('Studio Exclusive');
});

it('updates an existing newsroom article', function (): void {
    $moderator = User::factory()->create([
        'role' => 'moderator',
    ]);
    $author = User::factory()->create();
    $category = studioNewsCategory([
        'name' => 'Editorial',
        'slug' => 'editorial',
    ]);
    $tag = studioNewsTag([
        'name' => 'Feature',
        'slug' => 'feature',
    ]);

    $article = NewsArticle::query()->create([
        'title' => 'Original newsroom article',
        'slug' => 'original-newsroom-article',
        'excerpt' => 'Original excerpt.',
        'content' => 'Original content.',
        'author_id' => $author->id,
        'type' => NewsArticle::TYPE_ANNOUNCEMENT,
        'status' => 'draft',
        'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
    ]);

    $this->actingAs($moderator)
        ->patch(route('studio.news.update', ['article' => $article->id]), [
            'title' => 'Updated newsroom article',
            'slug' => 'updated-newsroom-article',
            'excerpt' => 'Updated excerpt.',
            'content' => '<p>Updated content.</p>',
            'type' => NewsArticle::TYPE_EDITORIAL,
            'category_id' => $category->id,
            'author_id' => $author->id,
            'editorial_status' => NewsArticle::EDITORIAL_STATUS_IN_REVIEW,
            'tag_ids' => [$tag->id],
            'new_tag_names' => ['Deep Dive'],
            'is_featured' => true,
            'is_pinned' => true,
        ])
        ->assertSessionHasNoErrors()
        ->assertRedirect();

    $article->refresh();

    expect($article->title)->toBe('Updated newsroom article')
        ->and($article->slug)->toBe('updated-newsroom-article')
        ->and($article->type)->toBe(NewsArticle::TYPE_EDITORIAL)
        ->and($article->editorial_status)->toBe(NewsArticle::EDITORIAL_STATUS_IN_REVIEW)
        ->and((int) $article->category_id)->toBe($category->id)
        ->and((bool) $article->is_featured)->toBeTrue()
        ->and((bool) $article->is_pinned)->toBeTrue()
        ->and($article->tags()->pluck('news_tags.name')->all())->toContain('Feature')
        ->and($article->tags()->pluck('news_tags.name')->all())->toContain('Deep Dive');
});

it('soft deletes a newsroom article from studio', function (): void {
    $moderator = User::factory()->create([
        'role' => 'moderator',
    ]);
    $author = User::factory()->create();

    $article = NewsArticle::query()->create([
        'title' => 'Delete me softly',
        'slug' => 'delete-me-softly',
        'excerpt' => 'Soft delete test article.',
        'content' => 'Studio delete content.',
        'author_id' => $author->id,
        'type' => NewsArticle::TYPE_EDITORIAL,
        'status' => 'draft',
        'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
    ]);

    $this->actingAs($moderator)
        ->delete(route('studio.news.destroy', ['article' => $article->id]))
        ->assertRedirect(route('studio.news.index'));

    $this->assertSoftDeleted('news_articles', [
        'id' => $article->id,
    ]);
});

it('uploads newsroom cover images with responsive variants and deletes them together', function (): void {
    Storage::fake('s3');

    config()->set('uploads.object_storage.disk', 's3');
    config()->set('cdn.files_url', 'https://cdn.skinbase.test');

    $moderator = User::factory()->create([
        'role' => 'moderator',
    ]);

    $uploadResponse = $this->actingAs($moderator)->postJson(route('api.studio.news.media.upload'), [
        'image' => UploadedFile::fake()->image('news-cover.jpg', 1600, 900),
    ]);

    $uploadResponse->assertOk();

    $path = (string) $uploadResponse->json('path');
    $mobileUrl = (string) $uploadResponse->json('mobile_url');
    $desktopUrl = (string) $uploadResponse->json('desktop_url');
    $srcset = (string) $uploadResponse->json('srcset');

    expect($path)->toMatch('#^news/covers/[a-f0-9]{2}/[a-f0-9]{2}/[a-f0-9]{64}\.webp$#');
    expect($mobileUrl)->toBe('https://cdn.skinbase.test/' . preg_replace('#\.webp$#', '-mobile.webp', $path));
    expect($desktopUrl)->toBe('https://cdn.skinbase.test/' . preg_replace('#\.webp$#', '-desktop.webp', $path));
    expect($srcset)->toContain($mobileUrl . ' 400w')
        ->toContain($desktopUrl . ' 768w');

    Storage::disk('s3')->assertExists($path);
    Storage::disk('s3')->assertExists(preg_replace('#\.webp$#', '-mobile.webp', $path));
    Storage::disk('s3')->assertExists(preg_replace('#\.webp$#', '-desktop.webp', $path));

    $this->actingAs($moderator)
        ->deleteJson(route('api.studio.news.media.destroy'), ['path' => $path])
        ->assertOk();

    Storage::disk('s3')->assertMissing($path);
    Storage::disk('s3')->assertMissing(preg_replace('#\.webp$#', '-mobile.webp', $path));
    Storage::disk('s3')->assertMissing(preg_replace('#\.webp$#', '-desktop.webp', $path));
});

it('backfills missing responsive variants for managed newsroom covers', function (): void {
    Storage::fake('s3');
    Http::fake([
        'https://api.cloudflare.com/client/v4/zones/test-zone/purge_cache' => Http::response(['success' => true], 200),
    ]);

    config()->set('uploads.object_storage.disk', 's3');
    config()->set('cdn.files_url', 'https://cdn.skinbase.test');
    config()->set('cdn.cloudflare.zone_id', 'test-zone');
    config()->set('cdn.cloudflare.api_token', 'test-token');

    $author = User::factory()->create();
    $category = studioNewsCategory();
    $masterPath = 'news/covers/aa/bb/' . str_repeat('a', 64) . '.webp';

    Storage::disk('s3')->put($masterPath, UploadedFile::fake()->image('source.jpg', 1600, 900)->get());

    NewsArticle::query()->create([
        'title' => 'Backfill cover variants',
        'slug' => 'backfill-cover-variants',
        'excerpt' => 'Backfill test.',
        'content' => 'Backfill test body.',
        'author_id' => $author->id,
        'category_id' => $category->id,
        'cover_image' => $masterPath,
        'type' => NewsArticle::TYPE_ANNOUNCEMENT,
        'status' => 'draft',
        'editorial_status' => NewsArticle::EDITORIAL_STATUS_DRAFT,
    ]);

    $this->artisan('news:generate-cover-thumbnails')
        ->assertSuccessful()
        ->expectsOutputToContain('generated=1');

    Storage::disk('s3')->assertExists('news/covers/aa/bb/' . str_repeat('a', 64) . '-mobile.webp');
    Storage::disk('s3')->assertExists('news/covers/aa/bb/' . str_repeat('a', 64) . '-desktop.webp');

    Http::assertNothingSent();

    $this->artisan('news:generate-cover-thumbnails', ['--force' => true])
        ->assertSuccessful()
        ->expectsOutputToContain('generated=1');

    Http::assertSent(function ($request) use ($masterPath): bool {
        return $request->url() === 'https://api.cloudflare.com/client/v4/zones/test-zone/purge_cache'
            && $request->hasHeader('Authorization', 'Bearer test-token')
            && $request['files'] === [
                'https://cdn.skinbase.test/' . preg_replace('#\.webp$#', '-mobile.webp', $masterPath),
                'https://cdn.skinbase.test/' . preg_replace('#\.webp$#', '-desktop.webp', $masterPath),
            ];
    });
});

it('searches news artwork entities without relying on a top-level views column', function (): void {
    $moderator = User::factory()->create([
        'role' => 'moderator',
    ]);

    $artwork = Artwork::factory()->create([
        'title' => 'Entity Search Artwork',
        'slug' => 'entity-search-artwork',
        'artwork_status' => 'published',
        'is_public' => true,
        'visibility' => Artwork::VISIBILITY_PUBLIC,
        'is_approved' => true,
        'published_at' => now()->subDay(),
    ]);

    $this->actingAs($moderator)
        ->getJson(route('studio.news.entity-search', [
            'type' => 'artwork',
            'q' => 'Entity Search',
        ]))
        ->assertOk()
        ->assertJsonFragment([
            'id' => $artwork->id,
            'title' => 'Entity Search Artwork',
        ]);
});