<?php

use App\Models\Cutting;
use App\Models\CuttingMaterial;
use App\Models\CuttingMaterialCombination;
use App\Models\RawMaterialPrice;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

/*
|--------------------------------------------------------------------------
| HELPERS
|--------------------------------------------------------------------------
*/

function giveCuttingPermissions(User $user): void
{
    $seeder = new RolePermissionSeeder;
    $seeder->run();

    $user->givePermissionTo([
        'cutting.view',
        'cutting.create',
        'cutting.update',
        'cutting.delete',
    ]);
}

function makeValidCuttingPayload(array $overrides = []): array
{
    $price = RawMaterialPrice::factory()->create(['stock' => 100]);

    return array_merge([
        'product_name' => 'Produk Test',
        'sample' => 5,
        'original_outside_sample' => 10,
        'cutting_result' => 15,
        'description' => 'Keterangan test',
        'materials' => [
            [
                'raw_material_price_id' => $price->id,
                'material_usage' => 20,
                'material_result' => 15,
                'combination_index' => null,
            ],
        ],
        'combinations' => [],
        'photo_key' => null,
    ], $overrides);
}

/*
|--------------------------------------------------------------------------
| AUTHENTICATION
|--------------------------------------------------------------------------
*/

test('guests are redirected to the login page', function () {
    $response = $this->get(route('admin.manage.cuttings.index'));
    $response->assertRedirect(route('login'));
});

test('guests are redirected when visiting create page', function () {
    $response = $this->get(route('admin.manage.cuttings.create'));
    $response->assertRedirect(route('login'));
});

test('guests are redirected when visiting edit page', function () {
    $cutting = Cutting::factory()->create();
    $response = $this->get(route('admin.manage.cuttings.edit', $cutting));
    $response->assertRedirect(route('login'));
});

test('guest cannot create cutting', function () {
    $response = $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload());
    $response->assertRedirect(route('login'));
    $this->assertDatabaseCount('cuttings', 0);
});

test('guest cannot update cutting', function () {
    $cutting = Cutting::factory()->create();
    $response = $this->put(route('admin.manage.cuttings.update', $cutting), makeValidCuttingPayload());
    $response->assertRedirect(route('login'));
});

test('guest cannot delete cutting', function () {
    $cutting = Cutting::factory()->create();
    $response = $this->delete(route('admin.manage.cuttings.destroy', $cutting));
    $response->assertRedirect(route('login'));
    $this->assertDatabaseHas('cuttings', ['id' => $cutting->id, 'deleted_at' => null]);
});

/*
|--------------------------------------------------------------------------
| AUTHORIZATION
|--------------------------------------------------------------------------
*/

test('user without permission cannot view cuttings', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

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

test('user without permission cannot visit create page', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

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

test('user without permission cannot store cutting', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload())->assertForbidden();
    $this->assertDatabaseCount('cuttings', 0);
});

test('user without permission cannot visit edit page', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $cutting = Cutting::factory()->create();
    $this->get(route('admin.manage.cuttings.edit', $cutting))->assertForbidden();
});

test('user without permission cannot update cutting', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $cutting = Cutting::factory()->create();
    $this->put(route('admin.manage.cuttings.update', $cutting), makeValidCuttingPayload())->assertForbidden();
});

test('user without permission cannot delete cutting', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $cutting = Cutting::factory()->create();
    $this->delete(route('admin.manage.cuttings.destroy', $cutting))->assertForbidden();
    $this->assertDatabaseHas('cuttings', ['id' => $cutting->id, 'deleted_at' => null]);
});

/*
|--------------------------------------------------------------------------
| INDEX
|--------------------------------------------------------------------------
*/

test('user with permission can view cuttings', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

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

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

/*
|--------------------------------------------------------------------------
| CREATE
|--------------------------------------------------------------------------
*/

test('user with permission can visit create page', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

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

/*
|--------------------------------------------------------------------------
| STORE - VALIDATION
|--------------------------------------------------------------------------
*/

test('store requires product_name', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $payload = makeValidCuttingPayload();
    unset($payload['product_name']);

    $this->post(route('admin.manage.cuttings.store'), $payload)
        ->assertSessionHasErrors('product_name');
});

test('store requires sample', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $payload = makeValidCuttingPayload();
    unset($payload['sample']);

    $this->post(route('admin.manage.cuttings.store'), $payload)
        ->assertSessionHasErrors('sample');
});

test('store requires original_outside_sample', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $payload = makeValidCuttingPayload();
    unset($payload['original_outside_sample']);

    $this->post(route('admin.manage.cuttings.store'), $payload)
        ->assertSessionHasErrors('original_outside_sample');
});

test('store requires cutting_result', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $payload = makeValidCuttingPayload();
    unset($payload['cutting_result']);

    $this->post(route('admin.manage.cuttings.store'), $payload)
        ->assertSessionHasErrors('cutting_result');
});

test('store requires at least one material', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $payload = makeValidCuttingPayload(['materials' => []]);

    $this->post(route('admin.manage.cuttings.store'), $payload)
        ->assertSessionHasErrors('materials');
});

test('store requires materials.raw_material_price_id', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $payload = makeValidCuttingPayload();
    unset($payload['materials'][0]['raw_material_price_id']);

    $this->post(route('admin.manage.cuttings.store'), $payload)
        ->assertSessionHasErrors('materials.0.raw_material_price_id');
});

test('store requires materials.material_usage', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $payload = makeValidCuttingPayload();
    unset($payload['materials'][0]['material_usage']);

    $this->post(route('admin.manage.cuttings.store'), $payload)
        ->assertSessionHasErrors('materials.0.material_usage');
});

test('store requires materials.material_result', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $payload = makeValidCuttingPayload();
    unset($payload['materials'][0]['material_result']);

    $this->post(route('admin.manage.cuttings.store'), $payload)
        ->assertSessionHasErrors('materials.0.material_result');
});

/*
|--------------------------------------------------------------------------
| STORE - SUCCESS
|--------------------------------------------------------------------------
*/

test('user with permission can store cutting', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload())
        ->assertRedirect(route('admin.manage.cuttings.index'));

    $this->assertDatabaseHas('cuttings', ['created_by_id' => $user->id]);
    $this->assertDatabaseHas('cutting_results', ['product_name' => 'Produk Test']);
});

test('store creates cutting material', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $price = RawMaterialPrice::factory()->create(['stock' => 100]);

    $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
        'materials' => [
            [
                'raw_material_price_id' => $price->id,
                'material_usage' => 20,
                'material_result' => 15,
                'combination_index' => null,
            ],
        ],
    ]))->assertRedirect();

    $cutting = Cutting::latest()->first();
    $this->assertDatabaseHas('cutting_materials', [
        'cutting_id' => $cutting->id,
        'raw_material_price_id' => $price->id,
        'material_usage' => 20,
        'material_result' => 15,
    ]);
});

test('store deducts stock from raw_material_price', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $price = RawMaterialPrice::factory()->create(['stock' => 100]);

    $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
        'materials' => [
            [
                'raw_material_price_id' => $price->id,
                'material_usage' => 30,
                'material_result' => 25,
                'combination_index' => null,
            ],
        ],
    ]))->assertRedirect();

    $price->refresh();
    $this->assertEquals(70, $price->stock);
});

test('store with combination creates combination record', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $price1 = RawMaterialPrice::factory()->create(['stock' => 100]);
    $price2 = RawMaterialPrice::factory()->create(['stock' => 50]);

    $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
        'materials' => [
            [
                'raw_material_price_id' => $price1->id,
                'material_usage' => 15,
                'material_result' => 12,
                'combination_index' => 0,
            ],
            [
                'raw_material_price_id' => $price2->id,
                'material_usage' => 10,
                'material_result' => 8,
                'combination_index' => 0,
            ],
        ],
        'combinations' => [
            ['material_result' => 20],
        ],
    ]))->assertRedirect();

    $cutting = Cutting::latest()->first();
    $this->assertDatabaseHas('cutting_material_combinations', [
        'cutting_id' => $cutting->id,
        'material_result' => 20,
    ]);

    $combination = CuttingMaterialCombination::where('cutting_id', $cutting->id)->first();
    $this->assertDatabaseHas('cutting_materials', [
        'cutting_id' => $cutting->id,
        'raw_material_price_id' => $price1->id,
        'combination_id' => $combination->id,
    ]);
    $this->assertDatabaseHas('cutting_materials', [
        'cutting_id' => $cutting->id,
        'raw_material_price_id' => $price2->id,
        'combination_id' => $combination->id,
    ]);
});

test('store deducts stock for multiple materials', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $price1 = RawMaterialPrice::factory()->create(['stock' => 100]);
    $price2 = RawMaterialPrice::factory()->create(['stock' => 50]);

    $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
        'materials' => [
            [
                'raw_material_price_id' => $price1->id,
                'material_usage' => 25,
                'material_result' => 20,
                'combination_index' => null,
            ],
            [
                'raw_material_price_id' => $price2->id,
                'material_usage' => 15,
                'material_result' => 12,
                'combination_index' => 0,
            ],
        ],
        'combinations' => [
            ['material_result' => 18],
        ],
    ]))->assertRedirect();

    $price1->refresh();
    $price2->refresh();
    $this->assertEquals(75, $price1->stock);
    $this->assertEquals(35, $price2->stock);
});

test('store with photo_key registers media', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
        'photo_key' => 'cuttings/test-photo.jpg',
    ]))->assertRedirect();

    $cutting = Cutting::latest()->first();
    $this->assertNotNull($cutting->getFirstMedia('photos'));
});

/*
|--------------------------------------------------------------------------
| STORE - STOCK VALIDATION
|--------------------------------------------------------------------------
*/

test('store fails when stock is insufficient', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $price = RawMaterialPrice::factory()->create(['stock' => 10]);

    $response = $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
        'materials' => [
            [
                'raw_material_price_id' => $price->id,
                'material_usage' => 20,
                'material_result' => 15,
                'combination_index' => null,
            ],
        ],
    ]));

    $response->assertRedirect();
    $this->assertDatabaseCount('cuttings', 0);

    $price->refresh();
    $this->assertEquals(10, $price->stock);
});

/*
|--------------------------------------------------------------------------
| EDIT
|--------------------------------------------------------------------------
*/

test('user with permission can visit edit page', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

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

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

/*
|--------------------------------------------------------------------------
| UPDATE - VALIDATION
|--------------------------------------------------------------------------
*/

test('update requires product_name', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

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

    $this->put(route('admin.manage.cuttings.update', $cutting), $payload)
        ->assertSessionHasErrors('product_name');
});

test('update requires sample', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

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

    $this->put(route('admin.manage.cuttings.update', $cutting), $payload)
        ->assertSessionHasErrors('sample');
});

test('update requires original_outside_sample', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

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

    $this->put(route('admin.manage.cuttings.update', $cutting), $payload)
        ->assertSessionHasErrors('original_outside_sample');
});

test('update requires cutting_result', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

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

    $this->put(route('admin.manage.cuttings.update', $cutting), $payload)
        ->assertSessionHasErrors('cutting_result');
});

/*
|--------------------------------------------------------------------------
| UPDATE - SUCCESS
|--------------------------------------------------------------------------
*/

test('user with permission can update cutting', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

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

    $this->put(route('admin.manage.cuttings.update', $cutting), makeValidCuttingPayload([
        'product_name' => 'Updated Product',
        'materials' => [
            [
                'raw_material_price_id' => $price->id,
                'material_usage' => 25,
                'material_result' => 20,
                'combination_index' => null,
            ],
        ],
    ]))->assertRedirect(route('admin.manage.cuttings.index'));

    $this->assertDatabaseHas('cutting_results', [
        'cutting_id' => $cutting->id,
        'product_name' => 'Updated Product',
    ]);
});

test('update adjusts stock correctly', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $price = RawMaterialPrice::factory()->create(['stock' => 100]);

    // First store with usage 20
    $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
        'materials' => [
            [
                'raw_material_price_id' => $price->id,
                'material_usage' => 20,
                'material_result' => 15,
                'combination_index' => null,
            ],
        ],
    ]));

    $price->refresh();
    $this->assertEquals(80, $price->stock);

    $cutting = Cutting::latest()->first();

    // Update: restore old (20), then deduct new (30) -> 80 + 20 - 30 = 70
    $this->put(route('admin.manage.cuttings.update', $cutting), makeValidCuttingPayload([
        'materials' => [
            [
                'raw_material_price_id' => $price->id,
                'material_usage' => 30,
                'material_result' => 25,
                'combination_index' => null,
            ],
        ],
    ]));

    $price->refresh();
    $this->assertEquals(70, $price->stock);
});

test('update fails when stock is insufficient after restore', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $price = RawMaterialPrice::factory()->create(['stock' => 5]);

    // Store with usage 3
    $this->post(route('admin.manage.cuttings.store'), makeValidCuttingPayload([
        'materials' => [
            [
                'raw_material_price_id' => $price->id,
                'material_usage' => 3,
                'material_result' => 2,
                'combination_index' => null,
            ],
        ],
    ]));

    $price->refresh();
    $this->assertEquals(2, $price->stock);

    $cutting = Cutting::latest()->first();

    // Update: restore old (3) -> 2 + 3 = 5, then try to deduct 10 -> fail
    // Stock remains at 5 because restore happens before validation
    $this->put(route('admin.manage.cuttings.update', $cutting), makeValidCuttingPayload([
        'materials' => [
            [
                'raw_material_price_id' => $price->id,
                'material_usage' => 10,
                'material_result' => 8,
                'combination_index' => null,
            ],
        ],
    ]))->assertRedirect();

    $price->refresh();
    $this->assertEquals(5, $price->stock);
});

/*
|--------------------------------------------------------------------------
| DESTROY - SUCCESS
|--------------------------------------------------------------------------
*/

test('user with permission can delete cutting', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

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

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

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

test('delete restores stock from raw_material_prices', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

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

    CuttingMaterial::create([
        'cutting_id' => $cutting->id,
        'raw_material_price_id' => $price->id,
        'material_usage' => 30,
        'material_result' => 25,
    ]);

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

    $price->refresh();
    $this->assertEquals(130, $price->stock);
});

test('delete restores stock for multiple materials', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $price1 = RawMaterialPrice::factory()->create(['stock' => 50]);
    $price2 = RawMaterialPrice::factory()->create(['stock' => 30]);
    $cutting = Cutting::factory()->create(['created_by_id' => $user->id]);

    CuttingMaterial::create([
        'cutting_id' => $cutting->id,
        'raw_material_price_id' => $price1->id,
        'material_usage' => 20,
        'material_result' => 15,
    ]);
    CuttingMaterial::create([
        'cutting_id' => $cutting->id,
        'raw_material_price_id' => $price2->id,
        'material_usage' => 10,
        'material_result' => 8,
    ]);

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

    $price1->refresh();
    $price2->refresh();
    $this->assertEquals(70, $price1->stock);
    $this->assertEquals(40, $price2->stock);
});

test('delete removes cutting_material_combinations', function () {
    $user = User::factory()->create();
    giveCuttingPermissions($user);
    $this->actingAs($user);

    $cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
    $combination = CuttingMaterialCombination::create([
        'cutting_id' => $cutting->id,
        'material_result' => 15,
    ]);

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

    $this->assertSoftDeleted('cutting_material_combinations', ['id' => $combination->id]);
});
