<?php

use App\Enums\CuttingStatus;
use App\Enums\Permission as PermissionEnum;
use App\Models\Category;
use App\Models\Cutting;
use App\Models\CuttingMaterial;
use App\Models\CuttingMaterialCombination;
use App\Models\CuttingResult;
use App\Models\Product;
use App\Models\RawMaterial;
use App\Models\RawMaterialPrice;
use App\Models\User;
use App\Services\Manage\CuttingService;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;

uses(RefreshDatabase::class);

beforeEach(function () {
    $this->seed(RolePermissionSeeder::class);
    Storage::fake('public');
});

// ─── Helper ───────────────────────────────────────────────

function createCuttingUserWithPermission(PermissionEnum ...$permissions): User
{
    $user = User::factory()->create();

    $user->givePermissionTo(
        array_merge(
            [PermissionEnum::DASHBOARD_VIEW->value],
            array_map(fn (PermissionEnum $p) => $p->value, $permissions)
        )
    );

    $user->forgetCachedPermissions();

    return $user;
}

function createCuttingWithMaterialsAndResults(?User $user = null): Cutting
{
    $user ??= User::factory()->create();

    $cutting = Cutting::factory()->create(['created_by_id' => $user->id]);

    $rawMaterial = RawMaterial::factory()->create();
    $price = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

    CuttingResult::factory()->create([
        'cutting_id' => $cutting->id,
        'product_name' => 'Test Product',
    ]);

    CuttingResult::factory()->create([
        'cutting_id' => $cutting->id,
        'product_name' => 'Second Product',
    ]);

    return $cutting;
}

function setupDraftItems(User $user): array
{
    $rawMaterial = RawMaterial::factory()->create();
    $price = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

    return ['price' => $price];
}

function makeResultData(): array
{
    return [
        'product_name' => 'Test Product',
        'cutting_result' => 10,
        'sample' => 8,
        'original_outside_sample' => 2,
    ];
}

// ─── Index ────────────────────────────────────────────────

describe('Cutting Index', function () {
    test('authenticated user with permission can view cutting index', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);

        $this->actingAs($user)
            ->get(route('admin.manage.cuttings.index'))
            ->assertOk();
    });

    test('guest is redirected to login', function () {
        $this->get(route('admin.manage.cuttings.index'))
            ->assertRedirect(route('login'));
    });

    test('user without permission is forbidden', function () {
        $user = User::factory()->create();

        $this->actingAs($user)
            ->get(route('admin.manage.cuttings.index'))
            ->assertForbidden();
    });

    test('index displays cuttings', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);

        createCuttingWithMaterialsAndResults($user);
        createCuttingWithMaterialsAndResults($user);

        $this->actingAs($user)
            ->get(route('admin.manage.cuttings.index'))
            ->assertOk();
    });

    test('index can search cuttings by description', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);

        Cutting::factory()->create([
            'created_by_id' => $user->id,
            'description' => 'Cutting kain batik',
        ]);

        $this->actingAs($user)
            ->get(route('admin.manage.cuttings.index', ['search' => 'batik']))
            ->assertOk();
    });
});

// ─── Create ───────────────────────────────────────────────

describe('Cutting Create', function () {
    test('authenticated user with permission can view create form', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $this->actingAs($user)
            ->get(route('admin.manage.cuttings.create'))
            ->assertOk();
    });

    test('guest is redirected to login', function () {
        $this->get(route('admin.manage.cuttings.create'))
            ->assertRedirect(route('login'));
    });

    test('user without create permission can still view create form', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);

        $this->actingAs($user)
            ->get(route('admin.manage.cuttings.create'))
            ->assertOk();
    });
});

// ─── Store ────────────────────────────────────────────────

describe('Cutting Store', function () {
    test('authenticated user with permission can create a cutting', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $draft = setupDraftItems($user);

        // Create draft material
        $this->actingAs($user)
            ->postJson(route('admin.manage.cuttings.draft_materials.store'), [
                'raw_material_price_id' => $draft['price']->id,
                'material_usage' => 5,
            ])
            ->assertOk();

        // Store cutting with results directly
        $this->actingAs($user)
            ->post(route('admin.manage.cuttings.store'), [
                'description' => 'Cutting test',
                'sewing_cost' => 50000,
                'other_cost' => 10000,
                'results' => [makeResultData()],
            ])
            ->assertRedirect(route('admin.manage.cuttings.index'));

        $cutting = Cutting::where('description', 'Cutting test')->first();
        expect($cutting)->not->toBeNull();
        expect($cutting->status)->toBe(CuttingStatus::IN_PROGRESS);
        expect($cutting->materials)->toHaveCount(1);
        expect($cutting->results)->toHaveCount(1);
    });

    test('draft material can have material_result', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $draft = setupDraftItems($user);

        $this->actingAs($user)
            ->postJson(route('admin.manage.cuttings.draft_materials.store'), [
                'raw_material_price_id' => $draft['price']->id,
                'material_usage' => 5,
                'material_result' => 10,
            ])
            ->assertOk()
            ->assertJsonPath('item.material_result', 10);

        $this->assertDatabaseHas('cutting_materials', [
            'raw_material_price_id' => $draft['price']->id,
            'material_result' => 10,
        ]);
    });

    test('material_result is preserved when creating cutting from drafts', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $draft = setupDraftItems($user);

        // Create draft material with material_result
        $this->actingAs($user)
            ->postJson(route('admin.manage.cuttings.draft_materials.store'), [
                'raw_material_price_id' => $draft['price']->id,
                'material_usage' => 5,
                'material_result' => 10,
            ])
            ->assertOk();

        // Store cutting with results directly
        $this->actingAs($user)
            ->post(route('admin.manage.cuttings.store'), [
                'description' => 'Cutting with result',
                'results' => [makeResultData()],
            ])
            ->assertRedirect(route('admin.manage.cuttings.index'));

        $cutting = Cutting::where('description', 'Cutting with result')->first();
        expect($cutting->materials->first()->material_result)->toBe(10);
    });

    test('guest cannot create a cutting', function () {
        $this->post(route('admin.manage.cuttings.store'), [
            'description' => 'Test',
        ])->assertRedirect(route('login'));
    });

    test('user without create permission is forbidden', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);

        $this->actingAs($user)
            ->post(route('admin.manage.cuttings.store'), [
                'description' => 'Test',
            ])
            ->assertForbidden();
    });

    test('store fails without draft materials', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        // Send results but no draft material
        $this->actingAs($user)
            ->post(route('admin.manage.cuttings.store'), [
                'description' => 'Test',
                'results' => [makeResultData()],
            ])
            ->assertSessionHasErrors('materials');
    });

    test('store fails without results', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $draft = setupDraftItems($user);

        // Create draft material but don't send results
        $this->actingAs($user)
            ->postJson(route('admin.manage.cuttings.draft_materials.store'), [
                'raw_material_price_id' => $draft['price']->id,
                'material_usage' => 5,
            ])
            ->assertOk();

        $this->actingAs($user)
            ->post(route('admin.manage.cuttings.store'), [
                'description' => 'Test',
            ])
            ->assertSessionHasErrors('results');
    });
});

// ─── Edit ─────────────────────────────────────────────────

describe('Cutting Edit', function () {
    test('authenticated user with permission can view edit form', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_UPDATE);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $this->actingAs($user)
            ->get(route('admin.manage.cuttings.edit', $cutting))
            ->assertOk();
    });

    test('guest is redirected to login', function () {
        $cutting = createCuttingWithMaterialsAndResults();

        $this->get(route('admin.manage.cuttings.edit', $cutting))
            ->assertRedirect(route('login'));
    });

    test('user without update permission can still view edit form', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $this->actingAs($user)
            ->get(route('admin.manage.cuttings.edit', $cutting))
            ->assertOk();
    });

    test('completed cutting cannot be edited', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_UPDATE);

        $cutting = Cutting::factory()->completed()->create(['created_by_id' => $user->id]);

        $this->actingAs($user)
            ->get(route('admin.manage.cuttings.edit', $cutting))
            ->assertRedirect(route('admin.manage.cuttings.index'));
    });
});

// ─── Update ───────────────────────────────────────────────

describe('Cutting Update', function () {
    test('authenticated user with permission can update a cutting', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_UPDATE);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $rawMaterial = RawMaterial::factory()->create();
        $price = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

        $this->actingAs($user)
            ->put(route('admin.manage.cuttings.update', $cutting), [
                'description' => 'Updated description',
                'sewing_cost' => 75000,
                'other_cost' => 15000,
                'materials' => [
                    ['raw_material_price_id' => $price->id, 'material_usage' => 10],
                ],
                'results' => [
                    [
                        'product_name' => 'Test Product',
                        'cutting_result' => 20,
                        'sample' => 15,
                        'original_outside_sample' => 5,
                    ],
                ],
            ])
            ->assertRedirect(route('admin.manage.cuttings.index'));

        expect($cutting->fresh()->description)->toBe('Updated description');
    });

    test('update can save material_result for single materials', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_UPDATE);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $rawMaterial = RawMaterial::factory()->create();
        $price = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

        $this->actingAs($user)
            ->put(route('admin.manage.cuttings.update', $cutting), [
                'description' => 'With material result',
                'sewing_cost' => 0,
                'other_cost' => 0,
                'materials' => [
                    ['raw_material_price_id' => $price->id, 'material_usage' => 10, 'material_result' => 25],
                ],
                'results' => [
                    [
                        'product_name' => 'Test Product',
                        'cutting_result' => 20,
                        'sample' => 15,
                        'original_outside_sample' => 5,
                    ],
                ],
            ])
            ->assertRedirect(route('admin.manage.cuttings.index'));

        $cutting->refresh();
        expect($cutting->materials->first()->material_result)->toBe(25);
    });

    test('guest cannot update a cutting', function () {
        $cutting = createCuttingWithMaterialsAndResults();

        $this->put(route('admin.manage.cuttings.update', $cutting), [
            'description' => 'Test',
            'materials' => [['raw_material_price_id' => 1, 'material_usage' => 1]],
            'results' => [['product_name' => 'P', 'cutting_result' => 1, 'sample' => 1, 'original_outside_sample' => 0]],
        ])->assertRedirect(route('login'));
    });

    test('user without update permission is forbidden', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $this->actingAs($user)
            ->put(route('admin.manage.cuttings.update', $cutting), [
                'description' => 'Test',
                'materials' => [['raw_material_price_id' => 1, 'material_usage' => 1]],
                'results' => [['product_name' => 'P', 'cutting_result' => 1, 'sample' => 1, 'original_outside_sample' => 0]],
            ])
            ->assertForbidden();
    });
});

// ─── Destroy ──────────────────────────────────────────────

describe('Cutting Destroy', function () {
    test('authenticated user with permission can delete a cutting', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_DELETE);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $this->actingAs($user)
            ->delete(route('admin.manage.cuttings.destroy', $cutting))
            ->assertRedirect(route('admin.manage.cuttings.index'));

        $this->assertSoftDeleted('cuttings', ['id' => $cutting->id]);
    });

    test('guest cannot delete a cutting', function () {
        $cutting = createCuttingWithMaterialsAndResults();

        $this->delete(route('admin.manage.cuttings.destroy', $cutting))
            ->assertRedirect(route('login'));

        $this->assertNotSoftDeleted('cuttings', ['id' => $cutting->id]);
    });

    test('user without delete permission can still delete cutting', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $this->actingAs($user)
            ->delete(route('admin.manage.cuttings.destroy', $cutting))
            ->assertRedirect(route('admin.manage.cuttings.index'));

        $this->assertSoftDeleted('cuttings', ['id' => $cutting->id]);
    });

    test('completed cutting cannot be deleted', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_DELETE);

        $cutting = Cutting::factory()->completed()->create(['created_by_id' => $user->id]);

        $this->actingAs($user)
            ->delete(route('admin.manage.cuttings.destroy', $cutting))
            ->assertSessionHasErrors('status');

        $this->assertNotSoftDeleted('cuttings', ['id' => $cutting->id]);
    });
});

// ─── Status Transition ────────────────────────────────────

describe('Cutting Status Transition', function () {
    test('authenticated user can complete a cutting', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_COMPLETE);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $this->actingAs($user)
            ->post(route('admin.manage.cuttings.transition_status', $cutting), [
                'status' => CuttingStatus::COMPLETED->value,
            ])
            ->assertRedirect(route('admin.manage.cuttings.index'));

        expect($cutting->fresh()->status)->toBe(CuttingStatus::COMPLETED);
    });

    test('guest cannot transition status', function () {
        $cutting = createCuttingWithMaterialsAndResults();

        $this->post(route('admin.manage.cuttings.transition_status', $cutting), [
            'status' => CuttingStatus::COMPLETED->value,
        ])->assertRedirect(route('login'));
    });

    test('user without permission cannot complete cutting', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $this->actingAs($user)
            ->post(route('admin.manage.cuttings.transition_status', $cutting), [
                'status' => CuttingStatus::COMPLETED->value,
            ])
            ->assertForbidden();
    });

    test('completed cutting cannot transition to another status', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_COMPLETE, PermissionEnum::CUTTINGS_UPDATE);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $this->actingAs($user)
            ->post(route('admin.manage.cuttings.transition_status', $cutting), [
                'status' => CuttingStatus::COMPLETED->value,
            ]);

        expect($cutting->fresh()->status)->toBe(CuttingStatus::COMPLETED);

        // COMPLETED is final, cannot transition to another status
        $this->actingAs($user)
            ->post(route('admin.manage.cuttings.transition_status', $cutting), [
                'status' => CuttingStatus::IN_PROGRESS->value,
            ])
            ->assertSessionHasErrors('status');
    });
});

// ─── Draft Combination ──────────────────────────────────────

describe('Draft Combination', function () {
    test('authenticated user with permission can create draft combination', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $rawMaterial = RawMaterial::factory()->create();
        $price1 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);
        $price2 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

        $this->actingAs($user)
            ->postJson(route('admin.manage.cuttings.draft_combinations.store'), [
                'materials' => [
                    ['raw_material_price_id' => $price1->id, 'material_usage' => 3],
                    ['raw_material_price_id' => $price2->id, 'material_usage' => 2],
                ],
            ])
            ->assertOk()
            ->assertJsonStructure(['items' => [['raw_material_price_id', 'combination_id']]]);

        $this->assertDatabaseCount('cutting_material_combinations', 1);
        $this->assertDatabaseCount('cutting_materials', 2);
    });

    test('draft combination can have material_result', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $rawMaterial = RawMaterial::factory()->create();
        $price1 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);
        $price2 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

        $this->actingAs($user)
            ->postJson(route('admin.manage.cuttings.draft_combinations.store'), [
                'materials' => [
                    ['raw_material_price_id' => $price1->id, 'material_usage' => 3],
                    ['raw_material_price_id' => $price2->id, 'material_usage' => 2],
                ],
                'material_result' => 15,
            ])
            ->assertOk();

        $combination = CuttingMaterialCombination::first();
        expect($combination->material_result)->toBe(15);
    });

    test('draft combination requires at least 2 materials', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $rawMaterial = RawMaterial::factory()->create();
        $price = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

        $this->actingAs($user)
            ->postJson(route('admin.manage.cuttings.draft_combinations.store'), [
                'materials' => [
                    ['raw_material_price_id' => $price->id, 'material_usage' => 3],
                ],
            ])
            ->assertUnprocessable();
    });

    test('authenticated user can delete draft combination', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $rawMaterial = RawMaterial::factory()->create();
        $price1 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);
        $price2 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

        $this->actingAs($user)
            ->postJson(route('admin.manage.cuttings.draft_combinations.store'), [
                'materials' => [
                    ['raw_material_price_id' => $price1->id, 'material_usage' => 3],
                    ['raw_material_price_id' => $price2->id, 'material_usage' => 2],
                ],
            ])
            ->assertOk();

        $combination = CuttingMaterialCombination::first();

        $this->actingAs($user)
            ->deleteJson(route('admin.manage.cuttings.draft_combinations.destroy', $combination->id))
            ->assertOk();

        $this->assertDatabaseCount('cutting_material_combinations', 0);
        $this->assertDatabaseCount('cutting_materials', 0);
    });

    test('user without permission cannot create draft combination', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);

        $rawMaterial = RawMaterial::factory()->create();
        $price1 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);
        $price2 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

        $this->actingAs($user)
            ->postJson(route('admin.manage.cuttings.draft_combinations.store'), [
                'materials' => [
                    ['raw_material_price_id' => $price1->id, 'material_usage' => 3],
                    ['raw_material_price_id' => $price2->id, 'material_usage' => 2],
                ],
            ])
            ->assertForbidden();
    });
});

// ─── Quick Create Product ─────────────────────────────────

describe('Quick Create Product', function () {
    test('authenticated user with permission can quick create product without prices', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $this->actingAs($user)
            ->postJson('/admin/manage/cuttings/quick-create-product', [
                'name' => 'Test Product',
                'description' => 'Test Description',
                'variants' => [
                    [
                        'name' => 'Varian 1',
                        'stock' => 10,
                        'retail_stock' => 5,
                    ],
                ],
            ])
            ->assertOk()
            ->assertJsonStructure(['product' => ['id', 'name']]);

        $this->assertDatabaseHas('products', ['name' => 'Test Product']);
        $this->assertDatabaseHas('product_variants', ['name' => 'Varian 1', 'stock' => 10]);
    });

    test('quick create product without images is allowed', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $this->actingAs($user)
            ->postJson('/admin/manage/cuttings/quick-create-product', [
                'name' => 'Product No Image',
                'variants' => [
                    [
                        'name' => 'Varian 1',
                        'stock' => 5,
                        'retail_stock' => 2,
                    ],
                ],
            ])
            ->assertOk();

        $this->assertDatabaseHas('products', ['name' => 'Product No Image']);
    });

    test('quick create product requires name', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $this->actingAs($user)
            ->postJson('/admin/manage/cuttings/quick-create-product', [
                'variants' => [
                    [
                        'name' => 'Varian 1',
                        'stock' => 5,
                        'retail_stock' => 2,
                    ],
                ],
            ])
            ->assertUnprocessable();
    });

    test('quick create product requires at least one variant', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $this->actingAs($user)
            ->postJson('/admin/manage/cuttings/quick-create-product', [
                'name' => 'Test Product',
                'variants' => [],
            ])
            ->assertUnprocessable();
    });

    test('quick create product with categories', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $category = Category::factory()->create();

        $this->actingAs($user)
            ->postJson('/admin/manage/cuttings/quick-create-product', [
                'name' => 'Product With Category',
                'category_ids' => [$category->id],
                'variants' => [
                    [
                        'name' => 'Varian 1',
                        'stock' => 5,
                        'retail_stock' => 2,
                    ],
                ],
            ])
            ->assertOk();

        $product = Product::where('name', 'Product With Category')->first();
        expect($product)->not->toBeNull();
        expect($product->categories)->toHaveCount(1);
    });

    test('user without permission cannot quick create product', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);

        $this->actingAs($user)
            ->postJson('/admin/manage/cuttings/quick-create-product', [
                'name' => 'Test Product',
                'variants' => [
                    [
                        'name' => 'Varian 1',
                        'stock' => 5,
                        'retail_stock' => 2,
                    ],
                ],
            ])
            ->assertForbidden();
    });
});

// ─── Store with Combination ─────────────────────────────────

describe('Cutting Store with Combination', function () {
    test('cutting can be created with combination materials', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $rawMaterial = RawMaterial::factory()->create();
        $price1 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);
        $price2 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

        // Create draft combination
        $this->actingAs($user)
            ->postJson(route('admin.manage.cuttings.draft_combinations.store'), [
                'materials' => [
                    ['raw_material_price_id' => $price1->id, 'material_usage' => 3],
                    ['raw_material_price_id' => $price2->id, 'material_usage' => 2],
                ],
            ])
            ->assertOk();

        // Store cutting with results directly
        $this->actingAs($user)
            ->post(route('admin.manage.cuttings.store'), [
                'description' => 'Cutting with combination',
                'results' => [makeResultData()],
            ])
            ->assertRedirect(route('admin.manage.cuttings.index'));

        $cutting = Cutting::where('description', 'Cutting with combination')->first();
        expect($cutting)->not->toBeNull();
        expect($cutting->materials)->toHaveCount(2);
        expect($cutting->materials->first()->combination_id)->not->toBeNull();
        expect($cutting->materials->first()->combination_id)->toBe($cutting->materials->last()->combination_id);
    });
});

// ─── Update with Combination ────────────────────────────────

describe('Cutting Update with Combination', function () {
    test('cutting can be updated with combination materials', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_UPDATE);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $rawMaterial = RawMaterial::factory()->create();
        $price1 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);
        $price2 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

        $this->actingAs($user)
            ->put(route('admin.manage.cuttings.update', $cutting), [
                'description' => 'Updated with combination',
                'sewing_cost' => 0,
                'other_cost' => 0,
                'materials' => [
                    ['raw_material_price_id' => $price1->id, 'material_usage' => 3, 'combination_id' => 999],
                    ['raw_material_price_id' => $price2->id, 'material_usage' => 2, 'combination_id' => 999],
                ],
                'results' => [
                    [
                        'product_name' => 'Test Product',
                        'cutting_result' => 10,
                        'sample' => 8,
                        'original_outside_sample' => 2,
                    ],
                ],
            ])
            ->assertRedirect(route('admin.manage.cuttings.index'));

        $cutting->refresh();
        expect($cutting->description)->toBe('Updated with combination');
        expect($cutting->materials)->toHaveCount(2);
        // Both materials should have the same combination_id
        expect($cutting->materials->first()->combination_id)->not->toBeNull();
        expect($cutting->materials->first()->combination_id)->toBe($cutting->materials->last()->combination_id);
    });

    test('update can save combination_material_result', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_UPDATE);

        $cutting = createCuttingWithMaterialsAndResults($user);

        $rawMaterial = RawMaterial::factory()->create();
        $price1 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);
        $price2 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

        $this->actingAs($user)
            ->put(route('admin.manage.cuttings.update', $cutting), [
                'description' => 'Combination with result',
                'sewing_cost' => 0,
                'other_cost' => 0,
                'materials' => [
                    ['raw_material_price_id' => $price1->id, 'material_usage' => 3, 'combination_id' => 999, 'combination_material_result' => 20],
                    ['raw_material_price_id' => $price2->id, 'material_usage' => 2, 'combination_id' => 999, 'combination_material_result' => 20],
                ],
                'results' => [
                    [
                        'product_name' => 'Test Product',
                        'cutting_result' => 10,
                        'sample' => 8,
                        'original_outside_sample' => 2,
                    ],
                ],
            ])
            ->assertRedirect(route('admin.manage.cuttings.index'));

        $cutting->refresh();
        $combination = $cutting->combinations()->first();
        expect($combination)->not->toBeNull();
        expect($combination->material_result)->toBe(20);
    });
});

// ─── Cutting Model ────────────────────────────────────────

describe('Cutting Model', function () {
    test('cutting uses soft deletes', function () {
        $cutting = Cutting::factory()->create();

        $cutting->delete();

        expect($cutting->trashed())->toBeTrue();
    });

    test('cutting has status cast to enum', function () {
        $cutting = Cutting::factory()->create(['status' => CuttingStatus::IN_PROGRESS->value]);

        expect($cutting->status)->toBe(CuttingStatus::IN_PROGRESS);
    });

    test('cutting has status label accessor', function () {
        $cutting = Cutting::factory()->create(['status' => CuttingStatus::IN_PROGRESS->value]);

        expect($cutting->status_label)->toBe('Proses');
    });

    test('cutting can have materials', function () {
        $cutting = Cutting::factory()->create();
        CuttingMaterial::factory()->count(3)->create(['cutting_id' => $cutting->id]);

        expect($cutting->fresh()->materials)->toHaveCount(3);
    });

    test('cutting can have results', function () {
        $cutting = Cutting::factory()->create();
        CuttingResult::factory()->count(2)->create(['cutting_id' => $cutting->id]);

        expect($cutting->fresh()->results)->toHaveCount(2);
    });

    test('cutting belongs to created by user', function () {
        $user = User::factory()->create();
        $cutting = Cutting::factory()->create(['created_by_id' => $user->id]);

        expect($cutting->createdBy)->not->toBeNull();
        expect($cutting->createdBy->id)->toBe($user->id);
    });

    test('cutting status is editable for in_progress', function () {
        $inProgress = Cutting::factory()->create(['status' => CuttingStatus::IN_PROGRESS->value]);

        expect($inProgress->status->isEditable())->toBeTrue();
    });
});

// ─── CuttingMaterial Model ─────────────────────────────────

describe('CuttingMaterial Model', function () {
    test('cutting material has material_usage cast to decimal', function () {
        $cutting = Cutting::factory()->create();
        $material = CuttingMaterial::factory()->create([
            'cutting_id' => $cutting->id,
            'material_usage' => 10.5,
        ]);

        expect((float) $material->material_usage)->toBe(10.5);
    });

    test('cutting material belongs to cutting', function () {
        $cutting = Cutting::factory()->create();
        $material = CuttingMaterial::factory()->create(['cutting_id' => $cutting->id]);

        expect($material->cutting)->not->toBeNull();
        expect($material->cutting->id)->toBe($cutting->id);
    });

    test('cutting material uses soft deletes', function () {
        $cutting = Cutting::factory()->create();
        $material = CuttingMaterial::factory()->create(['cutting_id' => $cutting->id]);

        $material->delete();

        expect($material->trashed())->toBeTrue();
    });

    test('cutting material has material_result cast to integer', function () {
        $cutting = Cutting::factory()->create();
        $material = CuttingMaterial::factory()->create([
            'cutting_id' => $cutting->id,
            'material_result' => 10,
        ]);

        expect($material->material_result)->toBeInt();
        expect($material->material_result)->toBe(10);
    });
});

// ─── CuttingResult Model ───────────────────────────────────

describe('CuttingResult Model', function () {
    test('cutting result has integer casts', function () {
        $cutting = Cutting::factory()->create();
        $result = CuttingResult::factory()->create([
            'cutting_id' => $cutting->id,
            'cutting_result' => 50,
            'sample' => 40,
            'original_outside_sample' => 10,
        ]);

        expect($result->cutting_result)->toBeInt();
        expect($result->sample)->toBeInt();
        expect($result->original_outside_sample)->toBeInt();
    });

    test('cutting result belongs to cutting', function () {
        $cutting = Cutting::factory()->create();
        $result = CuttingResult::factory()->create(['cutting_id' => $cutting->id]);

        expect($result->cutting)->not->toBeNull();
        expect($result->cutting->id)->toBe($cutting->id);
    });

    test('cutting result uses soft deletes', function () {
        $cutting = Cutting::factory()->create();
        $result = CuttingResult::factory()->create(['cutting_id' => $cutting->id]);

        $result->delete();

        expect($result->trashed())->toBeTrue();
    });
});

// ─── CuttingMaterialCombination Model ────────────────────────

describe('CuttingMaterialCombination Model', function () {
    test('combination uses soft deletes', function () {
        $combination = CuttingMaterialCombination::factory()->create();

        $combination->delete();

        expect($combination->trashed())->toBeTrue();
    });

    test('combination belongs to cutting', function () {
        $cutting = Cutting::factory()->create();
        $combination = CuttingMaterialCombination::factory()->create([
            'cutting_id' => $cutting->id,
        ]);

        expect($combination->cutting)->not->toBeNull();
        expect($combination->cutting->id)->toBe($cutting->id);
    });

    test('combination has many materials', function () {
        $combination = CuttingMaterialCombination::factory()->create();

        $rawMaterial = RawMaterial::factory()->create();
        $price1 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);
        $price2 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]);

        CuttingMaterial::factory()->create([
            'combination_id' => $combination->id,
            'raw_material_price_id' => $price1->id,
        ]);
        CuttingMaterial::factory()->create([
            'combination_id' => $combination->id,
            'raw_material_price_id' => $price2->id,
        ]);

        expect($combination->materials)->toHaveCount(2);
    });

    test('combination can be draft (cutting_id null)', function () {
        $user = User::factory()->create();
        $combination = CuttingMaterialCombination::factory()->create([
            'user_id' => $user->id,
            'cutting_id' => null,
        ]);

        expect($combination->cutting_id)->toBeNull();
        expect($combination->user_id)->toBe($user->id);
    });

    test('combination has material_result cast to integer', function () {
        $combination = CuttingMaterialCombination::factory()->create([
            'material_result' => 15,
        ]);

        expect($combination->material_result)->toBeInt();
        expect($combination->material_result)->toBe(15);
    });
});

// ─── Raw Material Catalog Stock Filter ───────────────────────

describe('Raw Material Catalog Stock Filter', function () {
    test('rawMaterialCatalog excludes variants with zero stock', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);

        $rawMaterial = RawMaterial::factory()->create(['is_active' => true]);
        $priceWithStock = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'stock' => 10]);
        $priceWithoutStock = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'stock' => 0]);

        $this->actingAs($user)
            ->get(route('admin.manage.cuttings.create'))
            ->assertOk();

        $service = app(CuttingService::class);
        $catalog = $service->rawMaterialCatalog(user: $user);
        $material = $catalog->firstWhere('id', $rawMaterial->id);

        $priceIds = $material->prices->pluck('id')->all();

        expect($priceIds)->toContain($priceWithStock->id);
        expect($priceIds)->not->toContain($priceWithoutStock->id);
    });

    test('rawMaterialCatalog includes zero-stock variants already used in cutting', function () {
        $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_UPDATE);

        $rawMaterial = RawMaterial::factory()->create(['is_active' => true]);
        $priceZeroStock = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'stock' => 0]);

        $cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
        CuttingMaterial::factory()->create([
            'cutting_id' => $cutting->id,
            'raw_material_price_id' => $priceZeroStock->id,
        ]);

        $service = app(CuttingService::class);
        $catalog = $service->rawMaterialCatalog($cutting);
        $material = $catalog->firstWhere('id', $rawMaterial->id);

        $priceIds = $material?->prices->pluck('id')->all() ?? [];

        expect($priceIds)->toContain($priceZeroStock->id);
    });
});
