<?php

declare(strict_types=1);

use Builder\Core\Foundation\Concerns\HasTranslations;
use Builder\Core\Foundation\Contracts\Translatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

final class TranslatableTestModel extends Model implements Translatable
{
    use HasTranslations;

    protected $table = 'categories';

    protected $guarded = [];

    /** @var list<string> */
    protected array $translatable = ['name', 'slug'];
}

final class TranslatableTestModelTranslation extends Model
{
    protected $table = 'category_translations';

    protected $guarded = [];
}

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

    Schema::create('category_translations', function ($table): void {
        $table->id();
        $table->foreignId('category_id')->constrained()->cascadeOnDelete();
        $table->string('locale', 10);
        $table->string('name');
        $table->string('slug');
        $table->timestamps();
        $table->unique(['category_id', 'locale']);
    });
});

it('returns correct translation table name', function (): void {
    $model = new TranslatableTestModel;

    expect($model->getTranslationTable())->toBe('category_translations');
});

it('returns correct translation foreign key', function (): void {
    $model = new TranslatableTestModel;

    expect($model->getTranslationForeignKey())->toBe('category_id');
});

it('returns translatable attributes from property', function (): void {
    $model = new TranslatableTestModel;

    expect($model->getTranslatableAttributes())->toBe(['name', 'slug']);
});

it('setTranslation and getTranslation round-trip', function (): void {
    $category = TranslatableTestModel::create([]);

    $category->setTranslation('en', ['name' => 'Technology', 'slug' => 'technology']);

    $translation = $category->getTranslation('en');

    expect($translation)->not->toBeNull()
        ->and($translation->name)->toBe('Technology')
        ->and($translation->slug)->toBe('technology');
});

it('getTranslation returns null for missing locale', function (): void {
    $category = TranslatableTestModel::create([]);

    expect($category->getTranslation('nl'))->toBeNull();
});

it('hasTranslation returns true when translation exists', function (): void {
    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Tech', 'slug' => 'tech']);

    expect($category->hasTranslation('en'))->toBeTrue();
});

it('hasTranslation returns false when translation does not exist', function (): void {
    $category = TranslatableTestModel::create([]);

    expect($category->hasTranslation('nl'))->toBeFalse();
});

it('getTranslations returns collection keyed by locale', function (): void {
    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Tech', 'slug' => 'tech']);
    $category->setTranslation('nl', ['name' => 'Technologie', 'slug' => 'technologie']);

    $translations = $category->getTranslations();

    expect($translations)->toHaveCount(2)
        ->and($translations->has('en'))->toBeTrue()
        ->and($translations->has('nl'))->toBeTrue()
        ->and($translations->get('en')->name)->toBe('Tech')
        ->and($translations->get('nl')->name)->toBe('Technologie');
});

it('setTranslation upserts on second call without duplicate', function (): void {
    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Old Name', 'slug' => 'old-name']);
    $category->setTranslation('en', ['name' => 'New Name', 'slug' => 'new-name']);

    $translations = $category->getTranslations();

    expect($translations)->toHaveCount(1)
        ->and($translations->get('en')->name)->toBe('New Name');
});

it('attribute accessor returns translated value for current app locale', function (): void {
    app()->setLocale('en');

    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Technology', 'slug' => 'technology']);

    $fresh = TranslatableTestModel::find($category->id);

    expect($fresh->name)->toBe('Technology')
        ->and($fresh->slug)->toBe('technology');
});

it('attribute accessor returns null when no translation exists for current locale', function (): void {
    app()->setLocale('nl');

    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Tech', 'slug' => 'tech']);

    $fresh = TranslatableTestModel::find($category->id);

    expect($fresh->name)->toBeNull();
});

it('attribute accessor resolves locale-specific value', function (): void {
    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Technology', 'slug' => 'technology']);
    $category->setTranslation('nl', ['name' => 'Technologie', 'slug' => 'technologie']);

    app()->setLocale('en');
    $fresh = TranslatableTestModel::find($category->id);
    expect($fresh->name)->toBe('Technology');

    app()->setLocale('nl');
    $fresh2 = TranslatableTestModel::find($category->id);
    expect($fresh2->name)->toBe('Technologie');
});

it('attribute accessor bypasses translation when attribute is already loaded', function (): void {
    app()->setLocale('en');

    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Technology', 'slug' => 'technology']);

    // Simulate a LEFT JOIN: Eloquent uses setRawAttributes() when hydrating from DB queries.
    $category->setRawAttributes(array_merge($category->getAttributes(), ['name' => 'Injected via JOIN']));

    expect($category->name)->toBe('Injected via JOIN');
});

it('toArray includes translatable attributes for current locale', function (): void {
    app()->setLocale('en');

    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Tech', 'slug' => 'tech']);

    $fresh = TranslatableTestModel::find($category->id);
    $array = $fresh->toArray();

    expect($array)->toHaveKey('name', 'Tech')
        ->and($array)->toHaveKey('slug', 'tech');
});

it('toArray includes null for missing translatable attributes', function (): void {
    app()->setLocale('nl');

    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Tech', 'slug' => 'tech']);

    $fresh = TranslatableTestModel::find($category->id);
    $array = $fresh->toArray();

    expect($array)->toHaveKey('name', null)
        ->and($array)->toHaveKey('slug', null);
});

it('toArray does not overwrite already loaded translatable attributes', function (): void {
    app()->setLocale('en');

    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Tech', 'slug' => 'tech']);

    // Simulate a JOIN-loaded attribute: Eloquent uses setRawAttributes() when hydrating from DB.
    $category->setRawAttributes(array_merge($category->getAttributes(), ['name' => 'From JOIN']));
    $array = $category->toArray();

    expect($array['name'])->toBe('From JOIN');
});

it('getTranslation caches result to avoid repeated queries', function (): void {
    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Tech', 'slug' => 'tech']);

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

    $category->getTranslation('en');
    $category->getTranslation('en');
    $category->getTranslation('en');

    expect($queryCount)->toBe(1);
});

it('setTranslation invalidates the cache for that locale', function (): void {
    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Old', 'slug' => 'old']);

    // Prime the cache
    $category->getTranslation('en');

    // Update should invalidate
    $category->setTranslation('en', ['name' => 'New', 'slug' => 'new']);

    expect($category->getTranslation('en')?->name)->toBe('New');
});

it('getTranslations populates the cache for all locales', function (): void {
    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Tech', 'slug' => 'tech']);
    $category->setTranslation('nl', ['name' => 'Technologie', 'slug' => 'technologie']);

    $category->getTranslations();

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

    // These should be served from cache — no additional queries
    $category->getTranslation('en');
    $category->getTranslation('nl');

    expect($queryCount)->toBe(0);
});

it('hasTranslation uses cache when available', function (): void {
    $category = TranslatableTestModel::create([]);
    $category->setTranslation('en', ['name' => 'Tech', 'slug' => 'tech']);

    // Prime the cache
    $category->getTranslation('en');

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

    expect($category->hasTranslation('en'))->toBeTrue();
    expect($queryCount)->toBe(0);
});
