<?php

use App\Enums\EmployeeAdvanceStatus;
use App\Enums\Permission as PermissionEnum;
use App\Models\CashAccount;
use App\Models\Employee;
use App\Models\EmployeeAdvance;
use App\Models\User;
use App\Models\UserProfile;
use Database\Seeders\CashAccountSeeder;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
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 createAdvanceUserWithPermission(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 createEmployeeUser(): User
{
    $user = User::factory()->create();
    UserProfile::factory()->create([
        'user_id' => $user->id,
        'full_name' => fake()->name(),
    ]);
    Employee::factory()->create(['user_id' => $user->id]);

    return $user;
}

function createEmployeeUserWithAdvancePermission(): User
{
    $user = createEmployeeUser();

    $user->givePermissionTo(
        PermissionEnum::DASHBOARD_VIEW->value,
        PermissionEnum::EMPLOYEE_ADVANCES_VIEW->value,
        PermissionEnum::EMPLOYEE_ADVANCES_CREATE->value,
        PermissionEnum::EMPLOYEE_ADVANCES_UPDATE->value,
        PermissionEnum::EMPLOYEE_ADVANCES_DELETE->value,
    );

    $user->forgetCachedPermissions();

    return $user;
}

function advancePayload(?int $amount = null, ?string $description = null, ?string $dueDate = null): array
{
    return [
        'amount' => $amount ?? 500000,
        'description' => $description ?? 'Kasbon untuk keperluan pribadi',
        'due_date' => $dueDate ?? now()->addMonth()->format('Y-m-d'),
    ];
}

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

describe('Employee Advance Index', function () {
    test('authenticated user with permission can view index', function () {
        $user = createAdvanceUserWithPermission(PermissionEnum::EMPLOYEE_ADVANCES_VIEW);

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

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

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

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

    test('employee can view their own advances', function () {
        $user = createEmployeeUserWithAdvancePermission();

        $employee = $user->employee;
        EmployeeAdvance::factory()->count(2)->create(['employee_id' => $employee->id]);

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

    test('index can filter by status', function () {
        $user = createAdvanceUserWithPermission(PermissionEnum::EMPLOYEE_ADVANCES_VIEW);

        $this->actingAs($user)
            ->get(route('admin.finance.employee_advances.index', ['status' => 'pending']))
            ->assertOk();
    });
});

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

describe('Employee Advance Store', function () {
    test('employee with permission can submit kasbon', function () {
        $user = createEmployeeUserWithAdvancePermission();

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.store'), advancePayload())
            ->assertRedirect(route('admin.finance.employee_advances.index'));

        $this->assertDatabaseHas('employee_advances', [
            'employee_id' => $user->employee->id,
            'amount' => 500000,
            'status' => EmployeeAdvanceStatus::PENDING->value,
        ]);
    });

    test('guest cannot submit kasbon', function () {
        $this->post(route('admin.finance.employee_advances.store'), advancePayload())
            ->assertRedirect(route('login'));
    });

    test('user without create permission cannot submit kasbon', function () {
        $user = createAdvanceUserWithPermission(PermissionEnum::EMPLOYEE_ADVANCES_VIEW);

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

    test('user without employee record cannot submit kasbon', function () {
        $user = createAdvanceUserWithPermission(
            PermissionEnum::EMPLOYEE_ADVANCES_VIEW,
            PermissionEnum::EMPLOYEE_ADVANCES_CREATE,
        );

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

    test('verifier cannot submit kasbon', function () {
        $user = createEmployeeUser();

        $user->givePermissionTo(
            PermissionEnum::DASHBOARD_VIEW->value,
            PermissionEnum::EMPLOYEE_ADVANCES_VIEW->value,
            PermissionEnum::EMPLOYEE_ADVANCES_VERIFY->value,
        );

        $user->forgetCachedPermissions();

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

    test('amount is required', function () {
        $user = createEmployeeUserWithAdvancePermission();

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.store'), [
                'amount' => '',
                'description' => 'Test',
                'due_date' => now()->addMonth()->format('Y-m-d'),
            ])
            ->assertSessionHasErrors('amount');
    });

    test('amount must be at least 1', function () {
        $user = createEmployeeUserWithAdvancePermission();

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.store'), advancePayload(amount: 0))
            ->assertSessionHasErrors('amount');
    });

    test('description is required', function () {
        $user = createEmployeeUserWithAdvancePermission();

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.store'), advancePayload(description: ''))
            ->assertSessionHasErrors('description');
    });

    test('due_date is required', function () {
        $user = createEmployeeUserWithAdvancePermission();

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.store'), advancePayload(dueDate: ''))
            ->assertSessionHasErrors('due_date');
    });

    test('due_date must be today or after', function () {
        $user = createEmployeeUserWithAdvancePermission();

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.store'), advancePayload(dueDate: '2020-01-01'))
            ->assertSessionHasErrors('due_date');
    });
});

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

describe('Employee Advance Update', function () {
    test('employee can update their own pending kasbon', function () {
        $user = createEmployeeUserWithAdvancePermission();

        $advance = EmployeeAdvance::factory()->create([
            'employee_id' => $user->employee->id,
            'status' => EmployeeAdvanceStatus::PENDING->value,
        ]);

        $this->actingAs($user)
            ->put(route('admin.finance.employee_advances.update', $advance), advancePayload(700000, 'Deskripsi baru'))
            ->assertRedirect(route('admin.finance.employee_advances.index'));

        expect($advance->fresh()->amount)->toBe(700000);
        expect($advance->fresh()->description)->toBe('Deskripsi baru');
    });

    test('guest cannot update kasbon', function () {
        $advance = EmployeeAdvance::factory()->create();

        $this->put(route('admin.finance.employee_advances.update', $advance), advancePayload())
            ->assertRedirect(route('login'));
    });

    test('employee cannot update others kasbon', function () {
        $user = createEmployeeUserWithAdvancePermission();

        $otherAdvance = EmployeeAdvance::factory()->create([
            'status' => EmployeeAdvanceStatus::PENDING->value,
        ]);

        $this->actingAs($user)
            ->put(route('admin.finance.employee_advances.update', $otherAdvance), advancePayload())
            ->assertForbidden();
    });

    test('cannot update non-pending kasbon', function () {
        $user = createEmployeeUserWithAdvancePermission();

        $advance = EmployeeAdvance::factory()->approved()->create([
            'employee_id' => $user->employee->id,
        ]);

        $this->actingAs($user)
            ->put(route('admin.finance.employee_advances.update', $advance), advancePayload())
            ->assertForbidden();
    });
});

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

describe('Employee Advance Destroy', function () {
    test('employee can delete their own kasbon', function () {
        $user = createEmployeeUserWithAdvancePermission();

        $advance = EmployeeAdvance::factory()->create([
            'employee_id' => $user->employee->id,
        ]);

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

        $this->assertDatabaseMissing('employee_advances', ['id' => $advance->id]);
    });

    test('guest cannot delete kasbon', function () {
        $advance = EmployeeAdvance::factory()->create();

        $this->delete(route('admin.finance.employee_advances.destroy', $advance))
            ->assertRedirect(route('login'));
    });

    test('user without delete permission cannot delete kasbon', function () {
        $user = createAdvanceUserWithPermission(PermissionEnum::EMPLOYEE_ADVANCES_VIEW);

        $advance = EmployeeAdvance::factory()->create();

        $this->actingAs($user)
            ->delete(route('admin.finance.employee_advances.destroy', $advance))
            ->assertForbidden();
    });
});

// ─── Approve ──────────────────────────────────────────────

describe('Employee Advance Approve', function () {
    test('user with verify permission can approve kasbon', function () {
        $user = createAdvanceUserWithPermission(
            PermissionEnum::EMPLOYEE_ADVANCES_VIEW,
            PermissionEnum::EMPLOYEE_ADVANCES_VERIFY,
        );

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

        $advance = EmployeeAdvance::factory()->create([
            'amount' => 500000,
            'status' => EmployeeAdvanceStatus::PENDING->value,
        ]);

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.approve', $advance))
            ->assertRedirect(route('admin.finance.employee_advances.index'));

        expect($advance->fresh()->status)->toBe(EmployeeAdvanceStatus::APPROVED);
        expect($advance->fresh()->verified_at)->not->toBeNull();
        expect($advance->fresh()->verified_by_id)->toBe($user->id);
    });

    test('guest cannot approve kasbon', function () {
        $advance = EmployeeAdvance::factory()->create();

        $this->post(route('admin.finance.employee_advances.approve', $advance))
            ->assertRedirect(route('login'));
    });

    test('user without verify permission cannot approve kasbon', function () {
        $user = createAdvanceUserWithPermission(PermissionEnum::EMPLOYEE_ADVANCES_VIEW);

        $advance = EmployeeAdvance::factory()->create();

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.approve', $advance))
            ->assertForbidden();
    });

    test('approving kasbon creates cash transaction', function () {
        $user = createAdvanceUserWithPermission(
            PermissionEnum::EMPLOYEE_ADVANCES_VIEW,
            PermissionEnum::EMPLOYEE_ADVANCES_VERIFY,
        );

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

        $advance = EmployeeAdvance::factory()->create([
            'amount' => 500000,
            'status' => EmployeeAdvanceStatus::PENDING->value,
        ]);

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.approve', $advance));

        expect($advance->fresh()->cash_transaction_id)->not->toBeNull();
    });
});

// ─── Reject ───────────────────────────────────────────────

describe('Employee Advance Reject', function () {
    test('user with verify permission can reject kasbon', function () {
        $user = createAdvanceUserWithPermission(
            PermissionEnum::EMPLOYEE_ADVANCES_VIEW,
            PermissionEnum::EMPLOYEE_ADVANCES_VERIFY,
        );

        $advance = EmployeeAdvance::factory()->create([
            'status' => EmployeeAdvanceStatus::PENDING->value,
        ]);

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.reject', $advance), [
                'reason' => 'Alasan ditolak',
            ])
            ->assertRedirect(route('admin.finance.employee_advances.index'));

        expect($advance->fresh()->status)->toBe(EmployeeAdvanceStatus::REJECTED);
    });

    test('guest cannot reject kasbon', function () {
        $advance = EmployeeAdvance::factory()->create();

        $this->post(route('admin.finance.employee_advances.reject', $advance), [
            'reason' => 'Alasan',
        ])->assertRedirect(route('login'));
    });

    test('user without verify permission cannot reject kasbon', function () {
        $user = createAdvanceUserWithPermission(PermissionEnum::EMPLOYEE_ADVANCES_VIEW);

        $advance = EmployeeAdvance::factory()->create();

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.reject', $advance), [
                'reason' => 'Alasan',
            ])
            ->assertForbidden();
    });

    test('reason is required for rejection', function () {
        $user = createAdvanceUserWithPermission(
            PermissionEnum::EMPLOYEE_ADVANCES_VIEW,
            PermissionEnum::EMPLOYEE_ADVANCES_VERIFY,
        );

        $advance = EmployeeAdvance::factory()->create();

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.reject', $advance), [
                'reason' => '',
            ])
            ->assertSessionHasErrors('reason');
    });

    test('rejecting kasbon creates rejection record', function () {
        $user = createAdvanceUserWithPermission(
            PermissionEnum::EMPLOYEE_ADVANCES_VIEW,
            PermissionEnum::EMPLOYEE_ADVANCES_VERIFY,
        );

        $advance = EmployeeAdvance::factory()->create([
            'status' => EmployeeAdvanceStatus::PENDING->value,
        ]);

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.reject', $advance), [
                'reason' => 'Budget tidak tersedia',
            ]);

        $this->assertDatabaseHas('rejections', [
            'rejectable_type' => EmployeeAdvance::class,
            'rejectable_id' => $advance->id,
            'reason' => 'Budget tidak tersedia',
        ]);
    });
});

// ─── Pay ──────────────────────────────────────────────────

describe('Employee Advance Pay', function () {
    test('user with pay permission can mark kasbon as paid', function () {
        $user = createAdvanceUserWithPermission(
            PermissionEnum::EMPLOYEE_ADVANCES_VIEW,
            PermissionEnum::EMPLOYEE_ADVANCES_PAY,
        );

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

        $advance = EmployeeAdvance::factory()->approved()->create([
            'amount' => 500000,
            'cash_transaction_id' => null,
        ]);

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.pay', $advance))
            ->assertRedirect(route('admin.finance.employee_advances.index'));

        expect($advance->fresh()->status)->toBe(EmployeeAdvanceStatus::PAID);
        expect($advance->fresh()->paid_at)->not->toBeNull();
    });

    test('guest cannot mark kasbon as paid', function () {
        $advance = EmployeeAdvance::factory()->approved()->create();

        $this->post(route('admin.finance.employee_advances.pay', $advance))
            ->assertRedirect(route('login'));
    });

    test('user without pay permission cannot mark kasbon as paid', function () {
        $user = createAdvanceUserWithPermission(PermissionEnum::EMPLOYEE_ADVANCES_VIEW);

        $advance = EmployeeAdvance::factory()->approved()->create();

        $this->actingAs($user)
            ->post(route('admin.finance.employee_advances.pay', $advance))
            ->assertForbidden();
    });
});

// ─── Employee Advance Model ───────────────────────────────

describe('Employee Advance Model', function () {
    test('employee advance has amount cast to integer', function () {
        $advance = EmployeeAdvance::factory()->create(['amount' => 1000000]);

        expect($advance->amount)->toBeInt();
        expect($advance->amount)->toBe(1000000);
    });

    test('employee advance has status cast to enum', function () {
        $advance = EmployeeAdvance::factory()->create([
            'status' => EmployeeAdvanceStatus::PENDING->value,
        ]);

        expect($advance->status)->toBe(EmployeeAdvanceStatus::PENDING);
    });

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

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

    test('employee advance has status label accessor', function () {
        $advance = EmployeeAdvance::factory()->create([
            'status' => EmployeeAdvanceStatus::PENDING->value,
        ]);

        expect($advance->status_label)->toBe('Menunggu');
    });

    test('employee advance belongs to employee', function () {
        $advance = EmployeeAdvance::factory()->create();

        expect($advance->employee)->not->toBeNull();
    });

    test('pending scope returns only pending advances', function () {
        EmployeeAdvance::factory()->count(2)->create([
            'status' => EmployeeAdvanceStatus::PENDING->value,
        ]);
        EmployeeAdvance::factory()->approved()->create();

        $pending = EmployeeAdvance::query()->pending()->get();

        expect($pending)->toHaveCount(2);
    });

    test('approved scope returns only approved advances', function () {
        EmployeeAdvance::factory()->count(3)->approved()->create();
        EmployeeAdvance::factory()->create([
            'status' => EmployeeAdvanceStatus::PENDING->value,
        ]);

        $approved = EmployeeAdvance::query()->approved()->get();

        expect($approved)->toHaveCount(3);
    });
});
