<?php

declare(strict_types=1);

use Builder\Core\Foundation\Concerns\HasPublishing;
use Builder\Core\Foundation\Concerns\HasTranslatablePublishing;
use Builder\Core\Foundation\Enums\PublicationStatus;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;

// ---------------------------------------------------------------------------
// Test doubles
// ---------------------------------------------------------------------------

final class TranslatablePublishTranslation extends Model
{
    use HasPublishing;

    protected $table = 'translatable_publish_translations';

    protected $guarded = [];
}

final class TranslatablePublishParent extends Model
{
    use HasTranslatablePublishing;

    protected $table = 'translatable_publish_parents';

    protected $guarded = [];

    public function translations(): HasMany
    {
        return $this->hasMany(TranslatablePublishTranslation::class, 'parent_id');
    }

    public function getTranslation(string $locale): ?TranslatablePublishTranslation
    {
        return $this->translations->firstWhere('locale', $locale)
            ?? $this->translations()->where('locale', $locale)->first();
    }
}

// ---------------------------------------------------------------------------
// Schema setup
// ---------------------------------------------------------------------------

beforeEach(function (): void {
    Schema::create('translatable_publish_parents', function ($table): void {
        $table->id();
        $table->timestamps();
    });

    Schema::create('translatable_publish_translations', function ($table): void {
        $table->id();
        $table->foreignId('parent_id');
        $table->string('locale');
        HasPublishing::publishingColumns($table);
        $table->timestamps();
    });
});

afterEach(function (): void {
    Schema::dropIfExists('translatable_publish_translations');
    Schema::dropIfExists('translatable_publish_parents');
});

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function makeTranslatableParentWithTranslations(): TranslatablePublishParent
{
    $parent = TranslatablePublishParent::create([]);
    $parent->translations()->createMany([
        ['locale' => 'en', 'status' => 'draft'],
        ['locale' => 'nl', 'status' => 'published'],
    ]);

    return $parent->load('translations');
}

// ---------------------------------------------------------------------------
// publish() tests
// ---------------------------------------------------------------------------

it('publish() with locale publishes only that locale', function (): void {
    $parent = makeTranslatableParentWithTranslations();

    $parent->publish('en');

    expect($parent->getTranslation('en')?->fresh()?->status)->toBe(PublicationStatus::Published)
        ->and($parent->getTranslation('nl')?->fresh()?->status)->toBe(PublicationStatus::Published);
});

it('publish() without locale publishes all translations', function (): void {
    $parent = makeTranslatableParentWithTranslations();

    $parent->publish();

    foreach ($parent->translations as $t) {
        expect($t->fresh()->status)->toBe(PublicationStatus::Published);
    }
});

// ---------------------------------------------------------------------------
// unpublish() tests
// ---------------------------------------------------------------------------

it('unpublish() with locale unpublishes only that locale', function (): void {
    $parent = makeTranslatableParentWithTranslations();

    $parent->unpublish('nl');

    expect($parent->getTranslation('nl')?->fresh()?->status)->toBe(PublicationStatus::Draft)
        ->and($parent->getTranslation('en')?->fresh()?->status)->toBe(PublicationStatus::Draft);
});

it('unpublish() without locale unpublishes all translations', function (): void {
    $parent = makeTranslatableParentWithTranslations();

    $parent->unpublish();

    foreach ($parent->translations as $t) {
        expect($t->fresh()->status)->toBe(PublicationStatus::Draft);
    }
});

// ---------------------------------------------------------------------------
// schedulePublish() tests
// ---------------------------------------------------------------------------

it('schedulePublish() with locale schedules only that locale', function (): void {
    $parent = makeTranslatableParentWithTranslations();
    $at = Carbon::parse('2030-06-01 12:00:00');

    $parent->schedulePublish($at, 'en');

    expect($parent->getTranslation('en')?->fresh()?->status)->toBe(PublicationStatus::Scheduled)
        ->and($parent->getTranslation('nl')?->fresh()?->status)->toBe(PublicationStatus::Published);
});

it('schedulePublish() without locale schedules all translations', function (): void {
    $parent = makeTranslatableParentWithTranslations();
    $at = Carbon::parse('2030-06-01 12:00:00');

    $parent->schedulePublish($at);

    foreach ($parent->translations as $t) {
        expect($t->fresh()->status)->toBe(PublicationStatus::Scheduled);
    }
});

// ---------------------------------------------------------------------------
// isPublished() / isDraft() / isScheduled() tests
// ---------------------------------------------------------------------------

it('isPublished() returns true when the given locale is published', function (): void {
    $parent = makeTranslatableParentWithTranslations();

    expect($parent->isPublished('nl'))->toBeTrue()
        ->and($parent->isPublished('en'))->toBeFalse();
});

it('isPublished() uses app locale when none given', function (): void {
    $parent = makeTranslatableParentWithTranslations();
    app()->setLocale('nl');

    expect($parent->isPublished())->toBeTrue();
});

it('isPublished() returns false when translation does not exist', function (): void {
    $parent = makeTranslatableParentWithTranslations();

    expect($parent->isPublished('de'))->toBeFalse();
});

it('isDraft() returns true when the given locale is draft', function (): void {
    $parent = makeTranslatableParentWithTranslations();

    expect($parent->isDraft('en'))->toBeTrue()
        ->and($parent->isDraft('nl'))->toBeFalse();
});

it('isDraft() returns true when translation does not exist', function (): void {
    $parent = makeTranslatableParentWithTranslations();

    expect($parent->isDraft('de'))->toBeTrue();
});

it('isScheduled() returns true when the given locale is scheduled', function (): void {
    $parent = makeTranslatableParentWithTranslations();
    $parent->schedulePublish(Carbon::parse('2030-01-01'), 'en');

    expect($parent->isScheduled('en'))->toBeTrue()
        ->and($parent->isScheduled('nl'))->toBeFalse();
});

it('isScheduled() returns false when translation does not exist', function (): void {
    $parent = makeTranslatableParentWithTranslations();

    expect($parent->isScheduled('de'))->toBeFalse();
});

// ---------------------------------------------------------------------------
// getStatusAttribute() tests
// ---------------------------------------------------------------------------

it('getStatusAttribute() returns current locale translation status value', function (): void {
    $parent = makeTranslatableParentWithTranslations();
    app()->setLocale('nl');

    expect($parent->getStatusAttribute())->toBe('published');
});

it('getStatusAttribute() returns null when no translation for current locale', function (): void {
    $parent = makeTranslatableParentWithTranslations();
    app()->setLocale('de');

    expect($parent->getStatusAttribute())->toBeNull();
});
