<?php

use App\Enums\Permission as PermissionEnum;
use App\Models\CashAccount;
use App\Models\CashTransaction;
use App\Models\Expense;
use App\Models\User;
use Database\Seeders\CashAccountSeeder;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;

uses(RefreshDatabase::class);

beforeEach(function () {
    $this->seed(RolePermissionSeeder::class);
    User::factory()->create(); // Required by CashAccountSeeder
    $this->seed(CashAccountSeeder::class);
    Storage::fake('public');
});

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

function createExpenseUserWithPermission(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 createExpenseWithCashTransaction(User $user): Expense
{
    $account = CashAccount::query()->first();
    $account->update(['balance' => 5000000]);

    $expense = Expense::factory()->create([
        'created_by_id' => $user->id,
        'amount' => 100000,
    ]);

    $cashTransaction = CashTransaction::factory()->create([
        'cash_account_id' => $account->id,
        'created_by_id' => $user->id,
        'reference_type' => Expense::class,
        'reference_id' => $expense->id,
        'amount' => $expense->amount,
        'balance_after' => $account->balance - $expense->amount,
    ]);

    $expense->update(['cash_transaction_id' => $cashTransaction->id]);

    return $expense;
}

function expensePayload(?int $amount = null, ?string $description = null): array
{
    return [
        'amount' => $amount ?? 100000,
        'description' => $description ?? 'Pembelian ATK',
        'photos' => [UploadedFile::fake()->image('bukti.jpg', 100, 100)],
    ];
}

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

describe('Expense Index', function () {
    test('authenticated user with permission can view expense index', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW);

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

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

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

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

    test('index displays expenses', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW);

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

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

    test('index can search expenses by description', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW);

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

        $this->actingAs($user)
            ->get(route('admin.finance.expenses.index', ['search' => 'kain']))
            ->assertOk();
    });
});

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

describe('Expense Store', function () {
    test('authenticated user with permission can create an expense', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW, PermissionEnum::EXPENSES_CREATE);

        CashAccount::query()->first()->update(['balance' => 5000000]);

        $this->actingAs($user)
            ->post(route('admin.finance.expenses.store'), expensePayload(250000, 'Beli mesin jahit'))
            ->assertRedirect(route('admin.finance.expenses.index'));

        $this->assertDatabaseHas('expenses', [
            'amount' => 250000,
            'description' => 'Beli mesin jahit',
        ]);
    });

    test('guest cannot create an expense', function () {
        $this->post(route('admin.finance.expenses.store'), expensePayload())
            ->assertRedirect(route('login'));
    });

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

        $this->actingAs($user)
            ->post(route('admin.finance.expenses.store'), expensePayload())
            ->assertForbidden();
    });

    test('amount is required', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW, PermissionEnum::EXPENSES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.finance.expenses.store'), [
                'amount' => '',
                'description' => 'Test',
                'photos' => [UploadedFile::fake()->image('bukti.jpg')],
            ])
            ->assertSessionHasErrors('amount');
    });

    test('amount must be at least 1', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW, PermissionEnum::EXPENSES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.finance.expenses.store'), [
                'amount' => 0,
                'description' => 'Test',
                'photos' => [UploadedFile::fake()->image('bukti.jpg')],
            ])
            ->assertSessionHasErrors('amount');
    });

    test('description is required', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW, PermissionEnum::EXPENSES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.finance.expenses.store'), [
                'amount' => 100000,
                'description' => '',
                'photos' => [UploadedFile::fake()->image('bukti.jpg')],
            ])
            ->assertSessionHasErrors('description');
    });

    test('description must not exceed 100 characters', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW, PermissionEnum::EXPENSES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.finance.expenses.store'), [
                'amount' => 100000,
                'description' => str_repeat('a', 101),
                'photos' => [UploadedFile::fake()->image('bukti.jpg')],
            ])
            ->assertSessionHasErrors('description');
    });

    test('creating expense also creates cash transaction', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW, PermissionEnum::EXPENSES_CREATE);

        CashAccount::query()->first()->update(['balance' => 5000000]);

        $this->actingAs($user)
            ->post(route('admin.finance.expenses.store'), expensePayload(300000, 'Operasional'));

        $expense = Expense::where('description', 'Operasional')->first();
        expect($expense)->not->toBeNull();
        expect($expense->cash_transaction_id)->not->toBeNull();

        $this->assertDatabaseHas('cash_transactions', [
            'id' => $expense->cash_transaction_id,
            'amount' => 300000,
        ]);
    });
});

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

describe('Expense Update', function () {
    test('authenticated user with permission can update an expense', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW, PermissionEnum::EXPENSES_UPDATE);

        $expense = createExpenseWithCashTransaction($user);

        $this->actingAs($user)
            ->put(route('admin.finance.expenses.update', $expense), expensePayload(200000, 'Deskripsi baru'))
            ->assertRedirect(route('admin.finance.expenses.index'));

        expect($expense->fresh()->amount)->toBe(200000);
        expect($expense->fresh()->description)->toBe('Deskripsi baru');
    });

    test('guest cannot update an expense', function () {
        $user = User::factory()->create();
        $expense = createExpenseWithCashTransaction($user);

        $this->put(route('admin.finance.expenses.update', $expense), expensePayload())
            ->assertRedirect(route('login'));
    });

    test('user without update permission is forbidden', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW);
        $expense = createExpenseWithCashTransaction($user);

        $this->actingAs($user)
            ->put(route('admin.finance.expenses.update', $expense), expensePayload())
            ->assertForbidden();
    });

    test('amount is required on update', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW, PermissionEnum::EXPENSES_UPDATE);
        $expense = createExpenseWithCashTransaction($user);

        $this->actingAs($user)
            ->put(route('admin.finance.expenses.update', $expense), [
                'amount' => '',
                'description' => 'Test',
                'photos' => [UploadedFile::fake()->image('bukti.jpg')],
            ])
            ->assertSessionHasErrors('amount');
    });

    test('description is required on update', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW, PermissionEnum::EXPENSES_UPDATE);
        $expense = createExpenseWithCashTransaction($user);

        $this->actingAs($user)
            ->put(route('admin.finance.expenses.update', $expense), [
                'amount' => 100000,
                'description' => '',
                'photos' => [UploadedFile::fake()->image('bukti.jpg')],
            ])
            ->assertSessionHasErrors('description');
    });
});

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

describe('Expense Destroy', function () {
    test('authenticated user with permission can delete an expense', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW, PermissionEnum::EXPENSES_DELETE);

        $expense = createExpenseWithCashTransaction($user);

        $this->actingAs($user)
            ->delete(route('admin.finance.expenses.destroy', $expense))
            ->assertRedirect(route('admin.finance.expenses.index'));

        $this->assertSoftDeleted('expenses', ['id' => $expense->id]);
    });

    test('guest cannot delete an expense', function () {
        $user = User::factory()->create();
        $expense = createExpenseWithCashTransaction($user);

        $this->delete(route('admin.finance.expenses.destroy', $expense))
            ->assertRedirect(route('login'));

        $this->assertNotSoftDeleted('expenses', ['id' => $expense->id]);
    });

    test('user without delete permission is forbidden', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW);
        $expense = createExpenseWithCashTransaction($user);

        $this->actingAs($user)
            ->delete(route('admin.finance.expenses.destroy', $expense))
            ->assertForbidden();

        $this->assertNotSoftDeleted('expenses', ['id' => $expense->id]);
    });

    test('deleting expense also deletes cash transaction', function () {
        $user = createExpenseUserWithPermission(PermissionEnum::EXPENSES_VIEW, PermissionEnum::EXPENSES_DELETE);

        $expense = createExpenseWithCashTransaction($user);
        $cashTransactionId = $expense->cash_transaction_id;

        $this->actingAs($user)
            ->delete(route('admin.finance.expenses.destroy', $expense));

        $this->assertSoftDeleted('expenses', ['id' => $expense->id]);
        $this->assertSoftDeleted('cash_transactions', ['id' => $cashTransactionId]);
    });
});

// ─── Expense Model ────────────────────────────────────────

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

        $expense->delete();

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

    test('expense has amount cast to integer', function () {
        $expense = Expense::factory()->create(['amount' => 500000]);

        expect($expense->amount)->toBeInt();
        expect($expense->amount)->toBe(500000);
    });

    test('expense has amount formatted accessor', function () {
        $expense = Expense::factory()->create(['amount' => 1500000]);

        expect($expense->amount_formatted)->toBe('Rp 1.500.000');
    });

    test('expense belongs to cash transaction', function () {
        $user = User::factory()->create();
        $expense = createExpenseWithCashTransaction($user);

        expect($expense->cashTransaction)->not->toBeNull();
    });

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

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