<?php

use App\Enums\Permission as PermissionEnum;
use App\Models\Purchase;
use App\Models\PurchaseItem;
use App\Models\RawMaterial;
use App\Models\RawMaterialPrice;
use App\Models\Supplier;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

uses(RefreshDatabase::class);

beforeEach(function () {
    $this->seed(RolePermissionSeeder::class);
});

function createPurchaseUserWithPermission(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 getPurchaseItemsPayload(RawMaterialPrice $price, float $quantity = 2): array
{
    $unit = $price->rawMaterial->unit;
    $unitVal = $unit instanceof BackedEnum ? $unit->value : $unit;

    return [
        [
            'raw_material_price_id' => $price->id,
            'name' => $price->rawMaterial->name,
            'unit' => $unitVal,
            'variant' => $price->variant,
            'quantity' => $quantity,
            'price' => $price->price,
        ],
    ];
}

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

describe('Purchase Index', function () {
    test('authenticated user with permission can view purchase index', function () {
        $user = createPurchaseUserWithPermission(PermissionEnum::PURCHASES_VIEW);

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

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

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

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

    test('index can search purchases by supplier name', function () {
        $user = createPurchaseUserWithPermission(PermissionEnum::PURCHASES_VIEW);

        $supplier = Supplier::factory()->create(['name' => 'Supplier Kain']);
        $purchase = Purchase::factory()->create(['supplier_id' => $supplier->id, 'created_by_id' => $user->id]);

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

    test('index can search purchases by id', function () {
        $user = createPurchaseUserWithPermission(PermissionEnum::PURCHASES_VIEW);

        $supplier = Supplier::factory()->create(['name' => 'Supplier Kain']);
        $purchase1 = Purchase::factory()->create(['supplier_id' => $supplier->id, 'created_by_id' => $user->id]);
        $purchase2 = Purchase::factory()->create(['supplier_id' => $supplier->id, 'created_by_id' => $user->id]);

        $response = $this->actingAs($user)
            ->get(route('admin.manage.purchases.index', ['search_id' => $purchase1->id]));

        $response->assertOk();
        $response->assertInertia(fn ($page) => $page
            ->has('purchases.data', 1)
            ->where('purchases.data.0.id', $purchase1->id)
        );
    });
});

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

describe('Purchase Create', function () {
    test('authenticated user with permission can view create form', function () {
        $user = createPurchaseUserWithPermission(PermissionEnum::PURCHASES_VIEW, PermissionEnum::PURCHASES_CREATE);

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

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

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

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

// ─── Store Validation ─────────────────────────────────────

describe('Purchase Store Validation', function () {
    test('supplier_id is required', function () {
        $user = createPurchaseUserWithPermission(PermissionEnum::PURCHASES_VIEW, PermissionEnum::PURCHASES_CREATE);

        $price = RawMaterialPrice::factory()->create();

        $this->actingAs($user)
            ->postJson(route('admin.manage.purchases.draft_items.store'), [
                'raw_material_price_id' => $price->id,
                'quantity' => 2,
            ])
            ->assertOk();

        $this->actingAs($user)
            ->post(route('admin.manage.purchases.store'), [
                'supplier_id' => '',
                'discount' => 0,
                'shipping_cost' => 0,
                'items' => getPurchaseItemsPayload($price, 2),
            ])
            ->assertSessionHasErrors('supplier_id');
    });

    test('supplier must exist', function () {
        $user = createPurchaseUserWithPermission(PermissionEnum::PURCHASES_VIEW, PermissionEnum::PURCHASES_CREATE);

        $price = RawMaterialPrice::factory()->create();

        $this->actingAs($user)
            ->postJson(route('admin.manage.purchases.draft_items.store'), [
                'raw_material_price_id' => $price->id,
                'quantity' => 2,
            ])
            ->assertOk();

        $this->actingAs($user)
            ->post(route('admin.manage.purchases.store'), [
                'supplier_id' => 99999,
                'discount' => 0,
                'shipping_cost' => 0,
                'items' => getPurchaseItemsPayload($price, 2),
            ])
            ->assertSessionHasErrors('supplier_id');
    });

    test('store fails without draft items', function () {
        $user = createPurchaseUserWithPermission(PermissionEnum::PURCHASES_VIEW, PermissionEnum::PURCHASES_CREATE);
        $supplier = Supplier::factory()->create();

        $this->actingAs($user)
            ->post(route('admin.manage.purchases.store'), [
                'supplier_id' => $supplier->id,
                'discount' => 0,
                'shipping_cost' => 0,
            ])
            ->assertSessionHasErrors('items');
    });
});

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

describe('Purchase Edit', function () {
    test('authenticated user with permission can view edit form', function () {
        $user = createPurchaseUserWithPermission(PermissionEnum::PURCHASES_VIEW, PermissionEnum::PURCHASES_CREATE, PermissionEnum::PURCHASES_UPDATE);

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

        $this->actingAs($user)
            ->postJson(route('admin.manage.purchases.draft_items.store'), [
                'raw_material_price_id' => $price->id,
                'quantity' => 2,
            ])
            ->assertOk();

        $this->actingAs($user)
            ->post(route('admin.manage.purchases.store'), [
                'supplier_id' => $supplier->id,
                'discount' => 0,
                'shipping_cost' => 0,
                'items' => getPurchaseItemsPayload($price, 2),
            ]);

        $purchase = Purchase::query()->latest()->firstOrFail();

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

    test('guest is redirected to login', function () {
        $purchase = Purchase::factory()->create();

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

    test('user without update permission is forbidden', function () {
        $user = createPurchaseUserWithPermission(PermissionEnum::PURCHASES_VIEW);
        $purchase = Purchase::factory()->create();

        $this->actingAs($user)
            ->get(route('admin.manage.purchases.edit', $purchase))
            ->assertForbidden();
    });
});

// ─── Update Validation ────────────────────────────────────

describe('Purchase Update Validation', function () {
    test('items is required on update', function () {
        $user = createPurchaseUserWithPermission(PermissionEnum::PURCHASES_VIEW, PermissionEnum::PURCHASES_CREATE, PermissionEnum::PURCHASES_UPDATE);

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

        $this->actingAs($user)
            ->postJson(route('admin.manage.purchases.draft_items.store'), [
                'raw_material_price_id' => $price->id,
                'quantity' => 2,
            ])
            ->assertOk();

        $this->actingAs($user)
            ->post(route('admin.manage.purchases.store'), [
                'supplier_id' => $supplier->id,
                'discount' => 0,
                'shipping_cost' => 0,
                'items' => getPurchaseItemsPayload($price, 2),
            ]);

        $purchase = Purchase::query()->latest()->firstOrFail();

        $this->actingAs($user)
            ->put(route('admin.manage.purchases.update', $purchase), [
                'supplier_id' => $supplier->id,
                'discount' => 0,
                'shipping_cost' => 0,
                'items' => [],
            ])
            ->assertSessionHasErrors('items');
    });
});

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

describe('Purchase Destroy', function () {
    test('guest cannot delete a purchase', function () {
        $purchase = Purchase::factory()->create();

        $this->delete(route('admin.manage.purchases.destroy', $purchase))
            ->assertRedirect(route('login'));
    });

    test('user without delete permission is forbidden', function () {
        $user = createPurchaseUserWithPermission(PermissionEnum::PURCHASES_VIEW);
        $purchase = Purchase::factory()->create();

        $this->actingAs($user)
            ->delete(route('admin.manage.purchases.destroy', $purchase))
            ->assertForbidden();
    });
});

// ─── Owner Direct Actions ──────────────────────────────────

describe('Owner Direct Actions', function () {
    test('owner can create purchase directly and increments stock', function () {
        $owner = createPurchaseUserWithPermission(
            PermissionEnum::PURCHASES_VIEW,
            PermissionEnum::PURCHASES_CREATE,
            PermissionEnum::OWNER_VERIFICATIONS_VERIFY,
        );

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

        $this->actingAs($owner)
            ->postJson(route('admin.manage.purchases.draft_items.store'), [
                'raw_material_price_id' => $price->id,
                'quantity' => 3,
            ])
            ->assertOk();

        $this->actingAs($owner)
            ->post(route('admin.manage.purchases.store'), [
                'supplier_id' => $supplier->id,
                'discount' => 0,
                'shipping_cost' => 0,
                'items' => getPurchaseItemsPayload($price, 3),
            ])
            ->assertRedirect(route('admin.manage.purchases.index'));

        $purchase = Purchase::query()->latest()->first();

        expect($purchase)->not->toBeNull();

        $this->assertDatabaseMissing('owner_verification_requests', [
            'subject_id' => $purchase->id,
            'subject_type' => Purchase::class,
        ]);

        expect((float) $price->fresh()->stock)->toBe(13.0);
    });

    test('owner can delete purchase directly and decrements stock', function () {
        $owner = createPurchaseUserWithPermission(
            PermissionEnum::PURCHASES_VIEW,
            PermissionEnum::PURCHASES_CREATE,
            PermissionEnum::PURCHASES_DELETE,
            PermissionEnum::OWNER_VERIFICATIONS_VERIFY,
        );

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

        $this->actingAs($owner)
            ->postJson(route('admin.manage.purchases.draft_items.store'), [
                'raw_material_price_id' => $price->id,
                'quantity' => 3,
            ])
            ->assertOk();

        $this->actingAs($owner)
            ->post(route('admin.manage.purchases.store'), [
                'supplier_id' => $supplier->id,
                'discount' => 0,
                'shipping_cost' => 0,
                'items' => getPurchaseItemsPayload($price, 3),
            ]);

        expect((float) $price->fresh()->stock)->toBe(13.0);

        $purchase = Purchase::query()->latest()->firstOrFail();

        $this->actingAs($owner)
            ->delete(route('admin.manage.purchases.destroy', $purchase))
            ->assertRedirect(route('admin.manage.purchases.index'));

        expect(Purchase::query()->whereKey($purchase->id)->exists())->toBeFalse();
        expect((float) $price->fresh()->stock)->toBe(10.0);
    });
});

// ─── Purchase Model ────────────────────────────────────────

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

        $purchase->delete();

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

    test('purchase has amounts cast to integer', function () {
        $purchase = Purchase::factory()->create([
            'subtotal' => 100000,
            'discount' => 5000,
            'shipping_cost' => 10000,
            'total' => 105000,
        ]);

        expect($purchase->subtotal)->toBeInt();
        expect($purchase->discount)->toBeInt();
        expect($purchase->shipping_cost)->toBeInt();
        expect($purchase->total)->toBeInt();
    });

    test('purchase has formatted amount accessors', function () {
        $purchase = Purchase::factory()->create([
            'subtotal' => 100000,
            'discount' => 5000,
            'shipping_cost' => 10000,
            'total' => 105000,
        ]);

        expect($purchase->subtotal_formatted)->toBe('Rp 100.000');
        expect($purchase->discount_formatted)->toBe('Rp 5.000');
        expect($purchase->shipping_cost_formatted)->toBe('Rp 10.000');
        expect($purchase->total_formatted)->toBe('Rp 105.000');
    });

    test('purchase belongs to supplier', function () {
        $supplier = Supplier::factory()->create();
        $purchase = Purchase::factory()->create(['supplier_id' => $supplier->id]);

        expect($purchase->supplier)->not->toBeNull();
        expect($purchase->supplier->id)->toBe($supplier->id);
    });

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

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

    test('purchase can have items', function () {
        $purchase = Purchase::factory()->create();
        PurchaseItem::factory()->count(3)->create(['purchase_id' => $purchase->id]);

        expect($purchase->fresh()->items)->toHaveCount(3);
    });
});

// ─── PurchaseItem Model ────────────────────────────────────

describe('PurchaseItem Model', function () {
    test('purchase item has amounts cast to integer', function () {
        $purchase = Purchase::factory()->create();
        $item = PurchaseItem::factory()->create([
            'purchase_id' => $purchase->id,
            'unit_price' => 50000,
            'subtotal' => 100000,
        ]);

        expect($item->unit_price)->toBeInt();
        expect($item->subtotal)->toBeInt();
    });

    test('purchase item has formatted amount accessors', function () {
        $purchase = Purchase::factory()->create();
        $item = PurchaseItem::factory()->create([
            'purchase_id' => $purchase->id,
            'unit_price' => 50000,
            'subtotal' => 100000,
        ]);

        expect($item->unit_price_formatted)->toBe('Rp 50.000');
        expect($item->subtotal_formatted)->toBe('Rp 100.000');
    });

    test('purchase item belongs to purchase', function () {
        $purchase = Purchase::factory()->create();
        $item = PurchaseItem::factory()->create(['purchase_id' => $purchase->id]);

        expect($item->purchase)->not->toBeNull();
        expect($item->purchase->id)->toBe($purchase->id);
    });

    test('purchase item uses soft deletes', function () {
        $purchase = Purchase::factory()->create();
        $item = PurchaseItem::factory()->create(['purchase_id' => $purchase->id]);

        $item->delete();

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

describe('Purchase POS New Raw Material Form', function () {
    test('store new raw material adds draft item to cart and initializes stock to 0', function () {
        $user = createPurchaseUserWithPermission(
            PermissionEnum::PURCHASES_VIEW,
            PermissionEnum::PURCHASES_CREATE,
        );

        $disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
        $key = 'raw-materials/'.Str::uuid().'.jpg';
        $imageContent = UploadedFile::fake()->image('Standard.jpg', 100, 100)->get();
        Storage::disk($disk)->put($key, $imageContent);

        $this->actingAs($user)
            ->postJson(route('admin.manage.purchases.draft_items.store_new_raw_material'), [
                'name' => 'Kain Toyobo Baru',
                'unit' => 'yard',
                'variant' => 'Standard',
                'price' => 25000,
                'quantity' => 10,
                's3_keys' => [$key],
            ])
            ->assertOk()
            ->assertJsonPath('item.raw_material_name', 'Kain Toyobo Baru')
            ->assertJsonPath('item.variant', 'Standard')
            ->assertJsonPath('item.unit_abbreviation', 'yard');

        $this->assertDatabaseHas('raw_materials', [
            'name' => 'Kain Toyobo Baru',
            'unit' => 'yard',
        ]);

        $this->assertDatabaseHas('raw_material_prices', [
            'variant' => 'Standard',
            'price' => 25000,
            'stock' => 0.0,
        ]);
    });

    test('store new raw material succeeds without photo/s3_keys', function () {
        $user = createPurchaseUserWithPermission(
            PermissionEnum::PURCHASES_VIEW,
            PermissionEnum::PURCHASES_CREATE,
        );

        $this->actingAs($user)
            ->postJson(route('admin.manage.purchases.draft_items.store_new_raw_material'), [
                'name' => 'Kain Toyobo Baru',
                'unit' => 'yard',
                'variant' => 'Standard',
                'price' => 25000,
                'quantity' => 10,
            ])
            ->assertOk()
            ->assertJsonPath('item.raw_material_name', 'Kain Toyobo Baru')
            ->assertJsonPath('item.variant', 'Standard')
            ->assertJsonPath('item.unit_abbreviation', 'yard');

        $this->assertDatabaseHas('raw_materials', [
            'name' => 'Kain Toyobo Baru',
            'unit' => 'yard',
        ]);

        $this->assertDatabaseHas('raw_material_prices', [
            'variant' => 'Standard',
            'price' => 25000,
            'stock' => 0.0,
        ]);

        $rawMaterialPrice = RawMaterialPrice::where('variant', 'Standard')
            ->whereHas('rawMaterial', fn ($q) => $q->where('name', 'Kain Toyobo Baru'))
            ->first();

        $this->assertDatabaseHas('purchase_items', [
            'raw_material_price_id' => $rawMaterialPrice->id,
            'quantity' => 10,
            'purchase_id' => null,
        ]);
    });

    test('store new raw material creates new records even if same name and unit exist', function () {
        $user = createPurchaseUserWithPermission(
            PermissionEnum::PURCHASES_VIEW,
            PermissionEnum::PURCHASES_CREATE,
        );

        $disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
        $key1 = 'raw-materials/'.Str::uuid().'.jpg';
        $key2 = 'raw-materials/'.Str::uuid().'.jpg';
        $imageContent = UploadedFile::fake()->image('Standard.jpg', 100, 100)->get();
        Storage::disk($disk)->put($key1, $imageContent);
        Storage::disk($disk)->put($key2, $imageContent);

        // Add first variant
        $this->actingAs($user)
            ->postJson(route('admin.manage.purchases.draft_items.store_new_raw_material'), [
                'name' => 'Kain Toyobo Unik',
                'unit' => 'yard',
                'variant' => 'Standard',
                'price' => 25000,
                'quantity' => 10,
                's3_keys' => [$key1],
            ])
            ->assertOk();

        // Add second variant with same name and unit
        $this->actingAs($user)
            ->postJson(route('admin.manage.purchases.draft_items.store_new_raw_material'), [
                'name' => 'Kain Toyobo Unik',
                'unit' => 'yard',
                'variant' => 'Premium',
                'price' => 30000,
                'quantity' => 5,
                's3_keys' => [$key2],
            ])
            ->assertOk();

        // Duplicates are fine — each creates its own raw_material
        $rawMaterialsCount = RawMaterial::where('name', 'Kain Toyobo Unik')->count();
        expect($rawMaterialsCount)->toBe(2);

        $this->assertDatabaseHas('raw_material_prices', [
            'variant' => 'Standard',
            'price' => 25000,
        ]);
        $this->assertDatabaseHas('raw_material_prices', [
            'variant' => 'Premium',
            'price' => 30000,
        ]);
    });

    test('store new raw material creates separate draft items for same variant name', function () {
        $user = createPurchaseUserWithPermission(
            PermissionEnum::PURCHASES_VIEW,
            PermissionEnum::PURCHASES_CREATE,
        );

        $disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
        $key1 = 'raw-materials/'.Str::uuid().'.jpg';
        $key2 = 'raw-materials/'.Str::uuid().'.jpg';
        $imageContent = UploadedFile::fake()->image('Standard.jpg', 100, 100)->get();
        Storage::disk($disk)->put($key1, $imageContent);
        Storage::disk($disk)->put($key2, $imageContent);

        // Add standard variant first time (qty = 10)
        $this->actingAs($user)
            ->postJson(route('admin.manage.purchases.draft_items.store_new_raw_material'), [
                'name' => 'Kain Toyobo Sama',
                'unit' => 'yard',
                'variant' => 'Standard',
                'price' => 25000,
                'quantity' => 10,
                's3_keys' => [$key1],
            ])
            ->assertOk()
            ->assertJsonPath('item.quantity', '10');

        // Add same variant second time — creates separate draft item (qty = 5)
        $this->actingAs($user)
            ->postJson(route('admin.manage.purchases.draft_items.store_new_raw_material'), [
                'name' => 'Kain Toyobo Sama',
                'unit' => 'yard',
                'variant' => 'Standard',
                'price' => 25000,
                'quantity' => 5,
                's3_keys' => [$key2],
            ])
            ->assertOk()
            ->assertJsonPath('item.quantity', '5');
    });
});
