<?php

use App\Enums\Permission as PermissionEnum;
use App\Models\CashAccount;
use App\Models\CashTransaction;
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 createCashUserWithPermission(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 createCashAccountWithBalance(int $balance = 1000000): CashAccount
{
    return CashAccount::factory()->create(['balance' => $balance]);
}

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

function withdrawPayload(?int $amount = null, ?string $description = null): array
{
    return [
        'amount' => $amount ?? 50000,
        'description' => $description ?? 'Penarikan kas',
        'photos' => [UploadedFile::fake()->image('bukti.jpg', 100, 100)],
    ];
}

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

describe('Cash Index', function () {
    test('authenticated user with permission can view cash index', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW);

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

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

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

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

    test('index displays cash transactions', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW);

        $account = CashAccount::query()->first();
        CashTransaction::factory()->count(3)->create([
            'cash_account_id' => $account->id,
            'created_by_id' => $user->id,
        ]);

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

    test('index can search transactions by description', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW);

        $account = CashAccount::query()->first();
        CashTransaction::factory()->create([
            'cash_account_id' => $account->id,
            'created_by_id' => $user->id,
            'description' => 'Setoran modal awal',
        ]);

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

// ─── Deposit ──────────────────────────────────────────────

describe('Cash Deposit', function () {
    test('authenticated user with permission can deposit', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_DEPOSIT);

        $this->actingAs($user)
            ->post(route('admin.finance.cash.deposit'), depositPayload(200000))
            ->assertRedirect(route('admin.finance.cash.index'));

        $account = CashAccount::query()->first();
        expect($account->fresh()->balance)->toBe(200000);
    });

    test('guest cannot deposit', function () {
        $this->post(route('admin.finance.cash.deposit'), depositPayload())
            ->assertRedirect(route('login'));
    });

    test('user without deposit permission is forbidden', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW);

        $this->actingAs($user)
            ->post(route('admin.finance.cash.deposit'), depositPayload())
            ->assertForbidden();
    });

    test('amount is required', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_DEPOSIT);

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

    test('amount must be at least 1', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_DEPOSIT);

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

    test('description is required', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_DEPOSIT);

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

    test('description must not exceed 100 characters', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_DEPOSIT);

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

    test('deposit creates a cash transaction', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_DEPOSIT);

        $this->actingAs($user)
            ->post(route('admin.finance.cash.deposit'), depositPayload(500000, 'Setoran modal'));

        $this->assertDatabaseHas('cash_transactions', [
            'description' => 'Setoran modal',
            'amount' => 500000,
        ]);
    });

    test('deposit updates account balance', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_DEPOSIT);

        $this->actingAs($user)
            ->post(route('admin.finance.cash.deposit'), depositPayload(300000));

        $account = CashAccount::query()->first();
        expect($account->fresh()->balance)->toBe(300000);

        $this->actingAs($user)
            ->post(route('admin.finance.cash.deposit'), depositPayload(200000));

        expect($account->fresh()->balance)->toBe(500000);
    });
});

// ─── Withdraw ─────────────────────────────────────────────

describe('Cash Withdraw', function () {
    test('authenticated user with permission can withdraw', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_WITHDRAW);

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

        $this->actingAs($user)
            ->post(route('admin.finance.cash.withdraw'), withdrawPayload(100000))
            ->assertRedirect(route('admin.finance.cash.index'));

        $account = CashAccount::query()->first();
        expect($account->fresh()->balance)->toBe(400000);
    });

    test('guest cannot withdraw', function () {
        $this->post(route('admin.finance.cash.withdraw'), withdrawPayload())
            ->assertRedirect(route('login'));
    });

    test('user without withdraw permission is forbidden', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW);

        $this->actingAs($user)
            ->post(route('admin.finance.cash.withdraw'), withdrawPayload())
            ->assertForbidden();
    });

    test('amount is required', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_WITHDRAW);

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

    test('amount must be at least 1', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_WITHDRAW);

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

    test('description is required', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_WITHDRAW);

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

    test('withdraw fails if insufficient balance', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_WITHDRAW);

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

        $this->actingAs($user)
            ->post(route('admin.finance.cash.withdraw'), withdrawPayload(100000))
            ->assertSessionHasErrors('amount');
    });

    test('withdraw creates a cash transaction', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_WITHDRAW);

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

        $this->actingAs($user)
            ->post(route('admin.finance.cash.withdraw'), withdrawPayload(200000, 'Pembelian bahan'));

        $this->assertDatabaseHas('cash_transactions', [
            'description' => 'Pembelian bahan',
            'amount' => 200000,
        ]);
    });
});

// ─── Update Transaction ───────────────────────────────────

describe('Cash Update Transaction', function () {
    test('authenticated user with permission can update a manual transaction', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_UPDATE);

        $account = CashAccount::query()->first();
        $transaction = CashTransaction::factory()->deposit()->create([
            'cash_account_id' => $account->id,
            'created_by_id' => $user->id,
            'amount' => 100000,
            'balance_after' => 100000,
        ]);

        $this->actingAs($user)
            ->put(route('admin.finance.cash.transactions.update', $transaction), [
                'amount' => 150000,
                'description' => 'Deskripsi diperbarui',
                'photos' => [UploadedFile::fake()->image('bukti.jpg')],
            ])
            ->assertRedirect(route('admin.finance.cash.index'));

        expect($transaction->fresh()->amount)->toBe(150000);
        expect($transaction->fresh()->description)->toBe('Deskripsi diperbarui');
    });

    test('guest cannot update a transaction', function () {
        $account = CashAccount::query()->first();
        $transaction = CashTransaction::factory()->deposit()->create([
            'cash_account_id' => $account->id,
        ]);

        $this->put(route('admin.finance.cash.transactions.update', $transaction), [
            'amount' => 100000,
            'description' => 'Test',
        ])->assertRedirect(route('login'));
    });

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

        $account = CashAccount::query()->first();
        $transaction = CashTransaction::factory()->deposit()->create([
            'cash_account_id' => $account->id,
        ]);

        $this->actingAs($user)
            ->put(route('admin.finance.cash.transactions.update', $transaction), [
                'amount' => 100000,
                'description' => 'Test',
            ])
            ->assertForbidden();
    });

    test('referenced transaction cannot be updated', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_UPDATE);

        $account = CashAccount::query()->first();
        $transaction = CashTransaction::factory()->create([
            'cash_account_id' => $account->id,
            'reference_type' => 'App\\Models\\Expense',
            'reference_id' => 1,
        ]);

        $this->actingAs($user)
            ->put(route('admin.finance.cash.transactions.update', $transaction), [
                'amount' => 100000,
                'description' => 'Test',
            ])
            ->assertForbidden();
    });
});

// ─── Destroy Transaction ──────────────────────────────────

describe('Cash Destroy Transaction', function () {
    test('authenticated user with permission can delete a manual transaction', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW, PermissionEnum::CASH_DELETE);

        $account = CashAccount::query()->first();
        $transaction = CashTransaction::factory()->deposit()->create([
            'cash_account_id' => $account->id,
            'created_by_id' => $user->id,
            'amount' => 100000,
            'balance_after' => 100000,
        ]);

        $this->actingAs($user)
            ->delete(route('admin.finance.cash.transactions.destroy', $transaction))
            ->assertRedirect(route('admin.finance.cash.index'));

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

    test('guest cannot delete a transaction', function () {
        $account = CashAccount::query()->first();
        $transaction = CashTransaction::factory()->deposit()->create([
            'cash_account_id' => $account->id,
        ]);

        $this->delete(route('admin.finance.cash.transactions.destroy', $transaction))
            ->assertRedirect(route('login'));

        $this->assertNotSoftDeleted('cash_transactions', ['id' => $transaction->id]);
    });

    test('user without delete permission is forbidden', function () {
        $user = createCashUserWithPermission(PermissionEnum::CASH_VIEW);

        $account = CashAccount::query()->first();
        $transaction = CashTransaction::factory()->deposit()->create([
            'cash_account_id' => $account->id,
        ]);

        $this->actingAs($user)
            ->delete(route('admin.finance.cash.transactions.destroy', $transaction))
            ->assertForbidden();

        $this->assertNotSoftDeleted('cash_transactions', ['id' => $transaction->id]);
    });
});

// ─── Cash Model ───────────────────────────────────────────

describe('Cash Model', function () {
    test('cash transaction uses soft deletes', function () {
        $account = CashAccount::query()->first();
        $transaction = CashTransaction::factory()->create([
            'cash_account_id' => $account->id,
        ]);

        $transaction->delete();

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

    test('cash account has balance cast to integer', function () {
        $account = CashAccount::factory()->create(['balance' => 500000]);

        expect($account->balance)->toBeInt();
        expect($account->balance)->toBe(500000);
    });

    test('cash account has balance formatted accessor', function () {
        $account = CashAccount::factory()->create(['balance' => 1500000]);

        expect($account->balance_formatted)->toBe('Rp 1.500.000');
    });

    test('cash transaction has amount formatted accessor', function () {
        $account = CashAccount::query()->first();
        $transaction = CashTransaction::factory()->create([
            'cash_account_id' => $account->id,
            'amount' => 2500000,
        ]);

        expect($transaction->amount_formatted)->toBe('Rp 2.500.000');
    });

    test('cash transaction belongs to cash account', function () {
        $account = CashAccount::query()->first();
        $transaction = CashTransaction::factory()->create([
            'cash_account_id' => $account->id,
        ]);

        expect($transaction->cashAccount)->not->toBeNull();
        expect($transaction->cashAccount->id)->toBe($account->id);
    });
});
