<?php

declare(strict_types=1);

use Builder\Core\Foundation\Concerns\HasTranslations;
use Builder\Core\Foundation\Contracts\Modules\BlockFieldHandler;
use Builder\Core\Foundation\Contracts\Modules\FieldHandler;
use Builder\Core\Foundation\Contracts\Translatable;
use Builder\Core\Foundation\Definition\Field;
use Builder\Core\Foundation\Definition\FieldBuilder;
use Builder\Core\Foundation\Resources\FieldHandlerRegistry;
use Builder\Core\Foundation\Resources\Resource;
use Builder\Core\Foundation\Resources\ResourceDefinition;
use Builder\Core\Foundation\Revisions\RevisionDiff;
use Builder\Core\Foundation\Revisions\RevisionService;
use Builder\Core\Models\Revision;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

final class RevisionTranslatableModel extends Model implements Translatable
{
    use HasTranslations;

    protected $table = 'revision_translatable_items';

    protected $guarded = [];

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

final class RevisionTranslatableModelTranslation extends Model
{
    protected $table = 'revision_translatable_item_translations';

    protected $guarded = [];
}

final class RevisionTranslatableResource extends Resource
{
    public static function getSlug(): string
    {
        return 'revision-translatable-items';
    }

    public static function getLabel(): string
    {
        return 'Revision Translatable Items';
    }

    /** @return class-string<Model> */
    public static function getModel(): string
    {
        return RevisionTranslatableModel::class;
    }

    /** @return array<string, mixed> */
    public function rules(string $action): array
    {
        return [];
    }

    public function isRevisable(): bool
    {
        return true;
    }
}

beforeEach(function (): void {
    Schema::create('revision_translatable_items', function (Blueprint $table): void {
        $table->id();
        $table->string('name')->default('');
        $table->timestamps();
    });

    Schema::create('revision_translatable_item_translations', function (Blueprint $table): void {
        $table->id();
        $table->foreignId('revision_translatable_item_id')->constrained('revision_translatable_items')->cascadeOnDelete();
        $table->string('locale', 10);
        $table->string('title')->nullable();
        $table->timestamps();
        $table->unique(['revision_translatable_item_id', 'locale']);
    });

    if (! Schema::hasTable('revisions')) {
        Schema::create('revisions', function (Blueprint $table): void {
            $table->id();
            $table->string('revisable_type');
            $table->unsignedBigInteger('revisable_id');
            $table->unsignedBigInteger('user_id')->nullable();
            $table->json('revision_data');
            $table->timestamps();
        });
    }
});

it('computeSnapshot includes translations for translatable model', function (): void {
    $service = app(RevisionService::class);

    $record = RevisionTranslatableModel::create(['name' => 'Test']);
    $record->setTranslation('en', ['title' => 'Hello']);
    $record->setTranslation('nl', ['title' => 'Hallo']);

    $snapshot = $service->computeSnapshot($record);

    expect($snapshot)->toHaveKey('_translations')
        ->and($snapshot['_translations']['en']['title'])->toBe('Hello')
        ->and($snapshot['_translations']['nl']['title'])->toBe('Hallo');
});

it('restore applies translations from revision data', function (): void {
    $service = app(RevisionService::class);

    $record = RevisionTranslatableModel::create(['name' => 'Test']);
    $record->setTranslation('en', ['title' => 'Updated']);

    $revision = Revision::create([
        'revisable_type' => RevisionTranslatableModel::class,
        'revisable_id' => $record->id,
        'user_id' => null,
        'revision_data' => [
            'name' => 'Test',
            '_translations' => [
                'en' => ['title' => 'Original'],
            ],
        ],
    ]);

    $service->restore($revision, $record);

    $translation = $record->fresh()?->getTranslation('en');
    expect($translation?->title)->toBe('Original');
});

it('capture includes translations in snapshot', function (): void {
    $service = app(RevisionService::class);
    $definition = ResourceDefinition::make(new RevisionTranslatableResource);

    $record = RevisionTranslatableModel::create(['name' => 'Test']);
    $record->setTranslation('en', ['title' => 'Hello']);

    $revision = $service->capture($record, $definition, null);

    expect($revision->revision_data)->toHaveKey('_translations')
        ->and($revision->revision_data['_translations']['en']['title'])->toBe('Hello');
});

// ---------------------------------------------------------------------------
// Translatable restore with _blocks in locale data (lines 65-68 RevisionService)
// ---------------------------------------------------------------------------

/**
 * Minimal BlockFieldHandler for translatable restore tests.
 */
final class RevisionTransBlockHandler implements BlockFieldHandler
{
    /** @var array<string, mixed> */
    public array $syncedFields = [];

    /** @param array<string, array<string, mixed>> $fields */
    public function loadFields(Model $record, array $fields, ?string $locale = null): void {}

    /**
     * @param  array<string, array<string, mixed>>  $fields
     * @param  array<string, mixed>  $requestData
     */
    public function syncFields(Model $record, array $fields, array $requestData, ?string $locale = null): void
    {
        foreach (array_keys($fields) as $fieldName) {
            $this->syncedFields["{$fieldName}:{$locale}"] = $requestData[$fieldName] ?? [];
        }
    }

    /**
     * @param  array<string, array<string, mixed>>  $blockFields
     * @param  array<string, mixed>  $requestData
     * @return array<string, list<string>>
     */
    public function rulesFor(array $blockFields, array $requestData): array
    {
        return [];
    }

    /**
     * @param  array<string, array<string, mixed>>  $blockFields
     * @param  array<string, mixed>  $requestData
     * @return array<string, string>
     */
    public function attributesFor(array $blockFields, array $requestData): array
    {
        return [];
    }
}

interface RevisionTransBlockHandlerInterface extends BlockFieldHandler {}

it('restore with translatable _blocks calls syncBlocks with locale', function (): void {
    $blockHandler = new RevisionTransBlockHandler;
    app()->instance(RevisionTransBlockHandlerInterface::class, $blockHandler);
    app(FieldHandlerRegistry::class)->register('TransBlockComponent', RevisionTransBlockHandlerInterface::class);

    $service = app(RevisionService::class);
    $record = RevisionTranslatableModel::create(['name' => 'Trans Blocks Restore']);

    $revision = Revision::create([
        'revisable_type' => RevisionTranslatableModel::class,
        'revisable_id' => $record->id,
        'user_id' => null,
        'revision_data' => [
            'name' => 'Trans Blocks Restore',
            '_translations' => [
                'en' => [
                    'title' => 'EN Title',
                    '_blocks' => [
                        'content' => [
                            ['key' => 'paragraph', 'data' => ['text' => 'Block Text'], 'media' => [], 'order' => 0],
                        ],
                    ],
                ],
            ],
        ],
    ]);

    $service->restore($revision, $record);

    // syncBlocks was called for the locale blocks
    expect($blockHandler->syncedFields)->not->toBeEmpty();
});

// ---------------------------------------------------------------------------
// captureHandlerFieldsIntoSnapshot for translatable model with block handler
// (RevisionService line 164, covers the translatable branch in the method)
// ---------------------------------------------------------------------------

final class RevisionTranslatableHandlerResource extends Resource
{
    public static function getSlug(): string
    {
        return 'revision-trans-handler-items';
    }

    public static function getLabel(): string
    {
        return 'Revision Trans Handler Items';
    }

    /** @return class-string<Model> */
    public static function getModel(): string
    {
        return RevisionTranslatableModel::class;
    }

    /** @return array<string, mixed> */
    public function rules(string $action): array
    {
        return [];
    }

    public function fields(): FieldBuilder
    {
        return FieldBuilder::make()->add(
            Field::text('name'),
            Field::text('content')->component('TransBlockComponent'),
        );
    }
}

it('computeSnapshot with translatable model and block handler captures handler fields', function (): void {
    $blockHandler = new RevisionTransBlockHandler;
    app()->instance(RevisionTransBlockHandlerInterface::class, $blockHandler);
    app(FieldHandlerRegistry::class)->register('TransBlockComponent', RevisionTransBlockHandlerInterface::class);

    $service = app(RevisionService::class);
    $definition = ResourceDefinition::make(new RevisionTranslatableHandlerResource);

    $record = RevisionTranslatableModel::create(['name' => 'Trans Handler Capture']);
    $record->setTranslation('en', ['title' => 'EN Title']);

    $snapshot = $service->computeSnapshot($record, $definition);

    expect($snapshot)->toBeArray()
        ->and($snapshot['name'])->toBe('Trans Handler Capture');
});

// ---------------------------------------------------------------------------
// captureHandlerFieldsIntoSnapshot: translatable model with non-block media handler
// (RevisionService lines 212-219 — the else branch for translatable+non-block)
// ---------------------------------------------------------------------------

/** Plain FieldHandler for translatable media snapshot tests. */
final class TransMediaHandler implements FieldHandler
{
    /** @param array<string, array<string, mixed>> $fields */
    public function loadFields(Model $record, array $fields, ?string $locale = null): void
    {
        foreach (array_keys($fields) as $fieldName) {
            // Set non-empty integer IDs to trigger the extractMediaIds path
            $record->setAttribute($fieldName, [1, 2, 3]);
        }
    }

    /**
     * @param  array<string, array<string, mixed>>  $fields
     * @param  array<string, mixed>  $requestData
     */
    public function syncFields(Model $record, array $fields, array $requestData, ?string $locale = null): void {}
}

interface TransMediaHandlerInterface extends FieldHandler {}

final class RevisionTranslatableMediaHandlerResource extends Resource
{
    public static function getSlug(): string
    {
        return 'revision-trans-media-items';
    }

    public static function getLabel(): string
    {
        return 'Revision Trans Media Items';
    }

    /** @return class-string<Model> */
    public static function getModel(): string
    {
        return RevisionTranslatableModel::class;
    }

    /** @return array<string, mixed> */
    public function rules(string $action): array
    {
        return [];
    }

    public function fields(): FieldBuilder
    {
        return FieldBuilder::make()->add(
            Field::text('name'),
            Field::text('media_field')->component('TransMediaComponent'),
        );
    }
}

it('computeSnapshot with translatable model and media handler captures media IDs per locale (lines 212-219)', function (): void {
    $mediaHandler = new TransMediaHandler;
    app()->instance(TransMediaHandlerInterface::class, $mediaHandler);
    app(FieldHandlerRegistry::class)->register('TransMediaComponent', TransMediaHandlerInterface::class);

    $service = app(RevisionService::class);
    $definition = ResourceDefinition::make(new RevisionTranslatableMediaHandlerResource);

    $record = RevisionTranslatableModel::create(['name' => 'Trans Media Capture']);
    $record->setTranslation('en', ['title' => 'EN Title']);

    $snapshot = $service->computeSnapshot($record, $definition);

    // The media handler set [1,2,3] on media_field; extractMediaIds should return [1,2,3]
    // and store it in _media keyed by collection_locale
    expect($snapshot)->toBeArray()
        ->and($snapshot['name'])->toBe('Trans Media Capture')
        ->and($snapshot)->toHaveKey(RevisionDiff::KEY_MEDIA);
});

// ---------------------------------------------------------------------------
// captureHandlerFieldsIntoSnapshot: translatable model with block handler
// where rawBlocks are NON-EMPTY (RevisionService lines 195-203, 209)
// (The `array_map` in the translatable+block branch)
// ---------------------------------------------------------------------------

/** BlockFieldHandler that sets non-empty block data on model attributes during loadFields. */
final class TransNonEmptyBlockHandler implements BlockFieldHandler
{
    /** @param array<string, array<string, mixed>> $fields */
    public function loadFields(Model $record, array $fields, ?string $locale = null): void
    {
        foreach (array_keys($fields) as $fieldName) {
            $record->setAttribute($fieldName, [
                ['key' => 'paragraph', 'data' => ['text' => 'Block Text'], 'media' => []],
            ]);
        }
    }

    /**
     * @param  array<string, array<string, mixed>>  $fields
     * @param  array<string, mixed>  $requestData
     */
    public function syncFields(Model $record, array $fields, array $requestData, ?string $locale = null): void {}

    /**
     * @param  array<string, array<string, mixed>>  $blockFields
     * @param  array<string, mixed>  $requestData
     * @return array<string, list<string>>
     */
    public function rulesFor(array $blockFields, array $requestData): array
    {
        return [];
    }

    /**
     * @param  array<string, array<string, mixed>>  $blockFields
     * @param  array<string, mixed>  $requestData
     * @return array<string, string>
     */
    public function attributesFor(array $blockFields, array $requestData): array
    {
        return [];
    }
}

interface TransNonEmptyBlockHandlerInterface extends BlockFieldHandler {}

final class RevisionTransNonEmptyBlockResource extends Resource
{
    public static function getSlug(): string
    {
        return 'revision-trans-nonempty-block-items';
    }

    public static function getLabel(): string
    {
        return 'Revision Trans NonEmpty Block Items';
    }

    /** @return class-string<Model> */
    public static function getModel(): string
    {
        return RevisionTranslatableModel::class;
    }

    /** @return array<string, mixed> */
    public function rules(string $action): array
    {
        return [];
    }

    public function fields(): FieldBuilder
    {
        return FieldBuilder::make()->add(
            Field::text('name'),
            Field::text('content_blocks')->component('TransNonEmptyBlockComponent'),
        );
    }
}

it('computeSnapshot with translatable model and block handler with non-empty rawBlocks captures locale blocks (lines 195-203, 209)', function (): void {
    $blockHandler = new TransNonEmptyBlockHandler;
    app()->instance(TransNonEmptyBlockHandlerInterface::class, $blockHandler);
    app(FieldHandlerRegistry::class)->register('TransNonEmptyBlockComponent', TransNonEmptyBlockHandlerInterface::class);

    $service = app(RevisionService::class);
    $definition = ResourceDefinition::make(new RevisionTransNonEmptyBlockResource);

    $record = RevisionTranslatableModel::create(['name' => 'Trans NonEmpty Blocks Capture']);
    $record->setTranslation('en', ['title' => 'EN Title']);

    $snapshot = $service->computeSnapshot($record, $definition);

    // The block handler set non-empty data via loadFields;
    // lines 195-203 (array_map) and 209 (assignment to KEY_TRANSLATIONS[locale][KEY_BLOCKS]) should be hit.
    expect($snapshot)->toBeArray()
        ->and($snapshot)->toHaveKey(RevisionDiff::KEY_TRANSLATIONS)
        ->and($snapshot[RevisionDiff::KEY_TRANSLATIONS])->toHaveKey('en')
        ->and($snapshot[RevisionDiff::KEY_TRANSLATIONS]['en'])->toHaveKey(RevisionDiff::KEY_BLOCKS);
});
