<?php

declare(strict_types=1);

use Builder\Core\Foundation\Definition\Field;
use Builder\Core\Foundation\Definition\FieldBuilder;
use Builder\Core\Foundation\Resources\NestedResource;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

// ─── Concrete test models ──────────────────────────────────────────────────────

final class NestedTestParent extends Model
{
    protected $table = 'nested_parents';

    protected $guarded = [];

    public $timestamps = false;
}

final class NestedTestChild extends Model
{
    protected $table = 'nested_children';

    protected $guarded = [];

    public $timestamps = false;
}

// ─── Concrete NestedResource implementation ────────────────────────────────────

final class ConcreteNestedResource extends NestedResource
{
    public static function getModel(): string
    {
        return NestedTestChild::class;
    }

    public static function parentField(): string
    {
        return 'parent_id';
    }

    public static function sortField(): ?string
    {
        return 'sort_order';
    }

    public function fields(): FieldBuilder
    {
        return FieldBuilder::make()->add(
            Field::text('title'),
        );
    }

    /** @param array<string, mixed> $data */
    protected function fillRecord(Model $record, array $data, Model $parent, int $index): void
    {
        parent::fillRecord($record, $data, $parent, $index);
        $record->fill(['title' => $data['title'] ?? '']);
    }
}

// ─── Helpers ───────────────────────────────────────────────────────────────────

function makeParent(): NestedTestParent
{
    return NestedTestParent::create([]);
}

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

    Schema::create('nested_children', function (Blueprint $table): void {
        $table->id();
        $table->unsignedBigInteger('parent_id');
        $table->string('title')->default('');
        $table->integer('sort_order')->default(0);
    });
});

// ─── fields() ─────────────────────────────────────────────────────────────────

it('returns field definitions', function (): void {
    $resource = new ConcreteNestedResource;
    $fields = $resource->fields()->toFields();
    expect($fields)->toHaveCount(1);
    expect($fields[0]->getName())->toBe('title');
});

// ─── loadForParent() ──────────────────────────────────────────────────────────

it('loads children ordered by sort_order', function (): void {
    $parent = makeParent();
    NestedTestChild::create(['parent_id' => $parent->id, 'title' => 'B', 'sort_order' => 1]);
    NestedTestChild::create(['parent_id' => $parent->id, 'title' => 'A', 'sort_order' => 0]);

    $resource = new ConcreteNestedResource;
    $items = $resource->loadForParent($parent);

    expect($items)->toHaveCount(2);
    expect($items[0]['title'])->toBe('A');
    expect($items[1]['title'])->toBe('B');
});

it('returns empty array when no children exist', function (): void {
    $parent = makeParent();

    $resource = new ConcreteNestedResource;
    expect($resource->loadForParent($parent))->toBeEmpty();
});

// ─── syncRecords() ────────────────────────────────────────────────────────────

it('creates new child records', function (): void {
    $parent = makeParent();

    (new ConcreteNestedResource)->syncRecords($parent, [
        ['id' => null, 'title' => 'First', 'sort_order' => 0],
        ['id' => null, 'title' => 'Second', 'sort_order' => 1],
    ]);

    expect(NestedTestChild::where('parent_id', $parent->id)->count())->toBe(2);
});

it('updates existing child records', function (): void {
    $parent = makeParent();
    $child = NestedTestChild::create(['parent_id' => $parent->id, 'title' => 'Old', 'sort_order' => 0]);

    (new ConcreteNestedResource)->syncRecords($parent, [
        ['id' => $child->id, 'title' => 'Updated', 'sort_order' => 0],
    ]);

    expect(NestedTestChild::find($child->id)?->title)->toBe('Updated');
});

it('deletes children not present in the sync payload', function (): void {
    $parent = makeParent();
    $kept = NestedTestChild::create(['parent_id' => $parent->id, 'title' => 'Keep', 'sort_order' => 0]);
    $removed = NestedTestChild::create(['parent_id' => $parent->id, 'title' => 'Remove', 'sort_order' => 1]);

    (new ConcreteNestedResource)->syncRecords($parent, [
        ['id' => $kept->id, 'title' => 'Keep', 'sort_order' => 0],
    ]);

    expect(NestedTestChild::find($removed->id))->toBeNull();
    expect(NestedTestChild::find($kept->id))->not->toBeNull();
});

it('assigns sort_order automatically when not provided', function (): void {
    $parent = makeParent();

    (new ConcreteNestedResource)->syncRecords($parent, [
        ['id' => null, 'title' => 'A'],
        ['id' => null, 'title' => 'B'],
        ['id' => null, 'title' => 'C'],
    ]);

    $children = NestedTestChild::where('parent_id', $parent->id)->orderBy('sort_order')->get();
    expect($children->pluck('sort_order')->all())->toBe([0, 1, 2]);
});

it('uses explicit sort_order from payload when provided', function (): void {
    $parent = makeParent();

    (new ConcreteNestedResource)->syncRecords($parent, [
        ['id' => null, 'title' => 'A', 'sort_order' => 10],
    ]);

    expect(NestedTestChild::where('parent_id', $parent->id)->first()->sort_order)->toBe(10);
});

it('only deletes children belonging to the given parent', function (): void {
    $parent1 = makeParent();
    $parent2 = makeParent();

    $child1 = NestedTestChild::create(['parent_id' => $parent1->id, 'title' => 'P1', 'sort_order' => 0]);
    $child2 = NestedTestChild::create(['parent_id' => $parent2->id, 'title' => 'P2', 'sort_order' => 0]);

    // Sync parent1 with empty list — should only delete parent1's children
    (new ConcreteNestedResource)->syncRecords($parent1, []);

    expect(NestedTestChild::find($child1->id))->toBeNull();
    expect(NestedTestChild::find($child2->id))->not->toBeNull();
});

// ─── Default method coverage ──────────────────────────────────────────────────

// A minimal nested resource without sortField override
final class UnsortableNestedResource extends NestedResource
{
    public static function getModel(): string
    {
        return NestedTestChild::class;
    }

    public static function parentField(): string
    {
        return 'parent_id';
    }

    public function fields(): FieldBuilder
    {
        return FieldBuilder::make();
    }
}

it('sortField() defaults to null', function (): void {
    expect(UnsortableNestedResource::sortField())->toBeNull();
});

it('isNestable() defaults to false', function (): void {
    expect(UnsortableNestedResource::isNestable())->toBeFalse();
});

it('rules() defaults to empty array', function (): void {
    expect((new UnsortableNestedResource)->rules('store'))->toBe([]);
});

it('eagerLoads() defaults to empty array', function (): void {
    expect((new UnsortableNestedResource)->eagerLoads())->toBe([]);
});

it('serializeRecord returns model toArray by default', function (): void {
    $parent = makeParent();
    $child = NestedTestChild::create(['parent_id' => $parent->id, 'title' => 'Test', 'sort_order' => 0]);

    $result = (new UnsortableNestedResource)->serializeRecord($child);

    expect($result)->toBeArray()->toHaveKey('title', 'Test');
});
