<?php

use App\Enums\PayrollAdjustmentType;
use App\Enums\PayrollPeriodStatus;
use App\Enums\PayrollStatus;
use App\Models\CashAccount;
use App\Models\Employee;
use App\Models\Payroll;
use App\Models\PayrollAdjustment;
use App\Models\PayrollPeriod;
use App\Models\User;
use Inertia\Testing\AssertableInertia as Assert;

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

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

test('guests are redirected to the login page from payroll period show', function () {
    $period = PayrollPeriod::factory()->create();
    $response = $this->get(route('admin.finance.payroll-periods.show', $period));
    $response->assertRedirect(route('login'));
});

test('guests are redirected to the login page from current', function () {
    $response = $this->get(route('admin.finance.payroll-periods.current'));
    $response->assertRedirect(route('login'));
});

test('guests are redirected to the login page from close', function () {
    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $response = $this->post(route('admin.finance.payroll-periods.close', $period));
    $response->assertRedirect(route('login'));
});

test('guests are redirected to the login page from reopen', function () {
    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::CLOSED]);
    $response = $this->post(route('admin.finance.payroll-periods.reopen', $period));
    $response->assertRedirect(route('login'));
});

test('guests are redirected to the login page from pay', function () {
    $payroll = Payroll::factory()->create(['status' => PayrollStatus::UNPAID]);
    $response = $this->post(route('admin.finance.payrolls.pay', $payroll));
    $response->assertRedirect(route('login'));
});

test('guests are redirected to the login page from cancel', function () {
    $payroll = Payroll::factory()->create(['status' => PayrollStatus::UNPAID]);
    $response = $this->post(route('admin.finance.payrolls.cancel', $payroll));
    $response->assertRedirect(route('login'));
});

test('guests are redirected to the login page from store adjustment', function () {
    $payroll = Payroll::factory()->create(['status' => PayrollStatus::UNPAID]);
    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 500000,
        'description' => 'Test',
    ]);
    $response->assertRedirect(route('login'));
});

test('guests are redirected to the login page from delete adjustment', function () {
    $adjustment = PayrollAdjustment::factory()->create();
    $response = $this->delete(route('admin.finance.payroll-adjustments.destroy', $adjustment));
    $response->assertRedirect(route('login'));
});

/*
|--------------------------------------------------------------------------
| PAYROLL PERIOD - INDEX
|--------------------------------------------------------------------------
*/

test('authenticated users can visit the payroll period index page', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $response = $this->get(route('admin.finance.payroll-periods.index'));
    $response->assertOk();
});

test('index page displays payroll periods', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['year' => 2026, 'month' => 7]);
    $employee = Employee::factory()->create(['base_salary' => 3000000]);
    Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'base_salary' => 3000000,
        'total_amount' => 3000000,
    ]);

    $response = $this->get(route('admin.finance.payroll-periods.index'));
    $response->assertOk();
    $response->assertInertia(fn (Assert $page) => $page
        ->component('admin/finance/payroll-period/index')
        ->has('payrollPeriods', 1)
    );
});

test('index page works when no payroll periods exist', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $response = $this->get(route('admin.finance.payroll-periods.index'));
    $response->assertOk();
    $response->assertInertia(fn (Assert $page) => $page
        ->component('admin/finance/payroll-period/index')
        ->has('payrollPeriods', 0)
    );
});

test('index page displays correct aggregates', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['year' => 2026, 'month' => 7]);
    $emp1 = Employee::factory()->create(['base_salary' => 3000000]);
    $emp2 = Employee::factory()->create(['base_salary' => 4000000]);

    Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $emp1->id,
        'base_salary' => 3000000,
        'bonus_amount' => 500000,
        'deduction_amount' => 100000,
        'total_amount' => 3400000,
        'status' => PayrollStatus::PAID,
    ]);
    Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $emp2->id,
        'base_salary' => 4000000,
        'bonus_amount' => 0,
        'deduction_amount' => 0,
        'total_amount' => 4000000,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->get(route('admin.finance.payroll-periods.index'));
    $response->assertOk();
    $response->assertInertia(fn (Assert $page) => $page
        ->component('admin/finance/payroll-period/index')
        ->has('payrollPeriods', 1)
        ->where('payrollPeriods.0.payrolls_count', 2)
        ->where('payrollPeriods.0.paid_count', 1)
    );
});

/*
|--------------------------------------------------------------------------
| PAYROLL PERIOD - SHOW
|--------------------------------------------------------------------------
*/

test('authenticated users can visit the payroll period show page', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['year' => 2026, 'month' => 7]);

    $response = $this->get(route('admin.finance.payroll-periods.show', $period));
    $response->assertOk();
});

test('show page displays payroll period detail with payrolls', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['year' => 2026, 'month' => 7]);
    $employee = Employee::factory()->create(['base_salary' => 3000000]);
    Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'base_salary' => 3000000,
        'total_amount' => 3000000,
    ]);

    $response = $this->get(route('admin.finance.payroll-periods.show', $period));
    $response->assertOk();
    $response->assertInertia(fn (Assert $page) => $page
        ->component('admin/finance/payroll-period/show')
        ->has('payrollPeriod')
        ->where('payrollPeriod.year', 2026)
        ->where('payrollPeriod.month', 7)
        ->has('payrollPeriod.payrolls', 1)
    );
});

/*
|--------------------------------------------------------------------------
| PAYROLL PERIOD - CURRENT
|--------------------------------------------------------------------------
*/

test('current route creates period if not exists and redirects', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $response = $this->get(route('admin.finance.payroll-periods.current'));
    $response->assertRedirect();

    $now = now();
    $this->assertDatabaseHas('payroll_periods', [
        'year' => $now->year,
        'month' => $now->month,
        'status' => PayrollPeriodStatus::OPEN->value,
    ]);
});

test('current route redirects to existing period', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $now = now();
    $period = PayrollPeriod::factory()->create([
        'year' => $now->year,
        'month' => $now->month,
        'status' => PayrollPeriodStatus::OPEN,
    ]);

    $response = $this->get(route('admin.finance.payroll-periods.current'));
    $response->assertRedirect();

    $this->assertDatabaseCount('payroll_periods', 1);
});

/*
|--------------------------------------------------------------------------
| PAYROLL PERIOD - CLOSE
|--------------------------------------------------------------------------
*/

test('open period can be closed when all payrolls are paid', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::PAID,
    ]);

    $response = $this->post(route('admin.finance.payroll-periods.close', $period));

    $response
        ->assertSessionHasNoErrors()
        ->assertRedirect(route('admin.finance.payroll-periods.index'));

    $period->refresh();
    expect($period->status)->toBe(PayrollPeriodStatus::CLOSED);
    expect($period->closed_by_id)->toBe($user->id);
    expect($period->closed_at)->not->toBeNull();
});

test('close fails when period already closed', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::CLOSED]);

    $response = $this->post(route('admin.finance.payroll-periods.close', $period));
    $response->assertRedirect();

    $period->refresh();
    expect($period->status)->toBe(PayrollPeriodStatus::CLOSED);
});

test('close fails when there are unpaid payrolls', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payroll-periods.close', $period));
    $response->assertRedirect();

    $period->refresh();
    expect($period->status)->toBe(PayrollPeriodStatus::OPEN);
});

/*
|--------------------------------------------------------------------------
| PAYROLL PERIOD - REOPEN
|--------------------------------------------------------------------------
*/

test('closed period can be reopened', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::CLOSED]);

    $response = $this->post(route('admin.finance.payroll-periods.reopen', $period));

    $response
        ->assertSessionHasNoErrors()
        ->assertRedirect(route('admin.finance.payroll-periods.index'));

    $period->refresh();
    expect($period->status)->toBe(PayrollPeriodStatus::OPEN);
    expect($period->closed_by_id)->toBeNull();
    expect($period->closed_at)->toBeNull();
});

test('reopen fails when period already open', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);

    $response = $this->post(route('admin.finance.payroll-periods.reopen', $period));
    $response->assertRedirect();

    $period->refresh();
    expect($period->status)->toBe(PayrollPeriodStatus::OPEN);
});

/*
|--------------------------------------------------------------------------
| PAYROLL - PAY
|--------------------------------------------------------------------------
*/

test('unpaid payroll can be paid', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    CashAccount::factory()->create(['balance' => 0]);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create(['base_salary' => 3000000]);
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'base_salary' => 3000000,
        'bonus_amount' => 500000,
        'deduction_amount' => 100000,
        'total_amount' => 3400000,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.pay', $payroll));

    $response
        ->assertSessionHasNoErrors()
        ->assertRedirect(route('admin.finance.payroll-periods.show', $period));

    $payroll->refresh();
    expect($payroll->status)->toBe(PayrollStatus::PAID);
    expect($payroll->paid_by_id)->toBe($user->id);
    expect($payroll->paid_at)->not->toBeNull();
    expect($payroll->cash_transaction_id)->not->toBeNull();

    expect(CashAccount::first()->balance)->toBe(3400000);
});

test('pay creates cash transaction with correct data', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    CashAccount::factory()->create(['balance' => 0]);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create(['base_salary' => 3000000]);
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'base_salary' => 3000000,
        'total_amount' => 3000000,
        'status' => PayrollStatus::UNPAID,
    ]);

    $this->post(route('admin.finance.payrolls.pay', $payroll));

    $this->assertDatabaseHas('cash_transactions', [
        'cash_account_id' => CashAccount::first()->id,
        'created_by_id' => $user->id,
        'amount' => 3000000,
        'type' => 'deposit',
        'description' => 'Pembayaran gaji karyawan',
    ]);
});

test('already paid payroll cannot be paid again', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    CashAccount::factory()->create(['balance' => 0]);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::PAID,
        'total_amount' => 3000000,
    ]);

    $response = $this->post(route('admin.finance.payrolls.pay', $payroll));
    $response->assertRedirect();

    expect(CashAccount::first()->balance)->toBe(0);
});

test('cancelled payroll cannot be paid', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    CashAccount::factory()->create(['balance' => 0]);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::CANCELLED,
    ]);

    $response = $this->post(route('admin.finance.payrolls.pay', $payroll));
    $response->assertRedirect();

    expect(CashAccount::first()->balance)->toBe(0);
});

test('paying non-existent payroll returns 404', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $response = $this->post(route('admin.finance.payrolls.pay', 999999));
    $response->assertStatus(404);
});

/*
|--------------------------------------------------------------------------
| PAYROLL - CANCEL
|--------------------------------------------------------------------------
*/

test('unpaid payroll can be cancelled', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.cancel', $payroll));

    $response
        ->assertSessionHasNoErrors()
        ->assertRedirect(route('admin.finance.payroll-periods.show', $period));

    $payroll->refresh();
    expect($payroll->status)->toBe(PayrollStatus::CANCELLED);
});

test('paid payroll cannot be cancelled', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::PAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.cancel', $payroll));
    $response->assertRedirect();

    $payroll->refresh();
    expect($payroll->status)->toBe(PayrollStatus::PAID);
});

test('already cancelled payroll cannot be cancelled again', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::CANCELLED,
    ]);

    $response = $this->post(route('admin.finance.payrolls.cancel', $payroll));
    $response->assertRedirect();

    $payroll->refresh();
    expect($payroll->status)->toBe(PayrollStatus::CANCELLED);
});

test('cancelling non-existent payroll returns 404', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $response = $this->post(route('admin.finance.payrolls.cancel', 999999));
    $response->assertStatus(404);
});

/*
|--------------------------------------------------------------------------
| PAYROLL ADJUSTMENT - STORE
|--------------------------------------------------------------------------
*/

test('bonus adjustment can be added to unpaid payroll', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create(['base_salary' => 3000000]);
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'base_salary' => 3000000,
        'bonus_amount' => 0,
        'deduction_amount' => 0,
        'total_amount' => 3000000,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 500000,
        'description' => 'Bonus performa',
    ]);

    $response
        ->assertSessionHasNoErrors()
        ->assertRedirect(route('admin.finance.payroll-periods.show', $period));

    $this->assertDatabaseHas('payroll_adjustments', [
        'payroll_id' => $payroll->id,
        'type' => PayrollAdjustmentType::BONUS->value,
        'amount' => 500000,
        'description' => 'Bonus performa',
    ]);

    $payroll->refresh();
    expect($payroll->bonus_amount)->toBe(500000);
    expect($payroll->total_amount)->toBe(3500000);
});

test('deduction adjustment can be added to unpaid payroll', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create(['base_salary' => 3000000]);
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'base_salary' => 3000000,
        'bonus_amount' => 0,
        'deduction_amount' => 0,
        'total_amount' => 3000000,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'deduction',
        'amount' => 200000,
        'description' => 'Potongan telat',
    ]);

    $response
        ->assertSessionHasNoErrors()
        ->assertRedirect(route('admin.finance.payroll-periods.show', $period));

    $payroll->refresh();
    expect($payroll->deduction_amount)->toBe(200000);
    expect($payroll->total_amount)->toBe(2800000);
});

test('multiple adjustments accumulate correctly', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create(['base_salary' => 3000000]);
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'base_salary' => 3000000,
        'bonus_amount' => 0,
        'deduction_amount' => 0,
        'total_amount' => 3000000,
        'status' => PayrollStatus::UNPAID,
    ]);

    $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 500000,
        'description' => 'Bonus 1',
    ]);

    $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 300000,
        'description' => 'Bonus 2',
    ]);

    $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'deduction',
        'amount' => 100000,
        'description' => 'Potongan 1',
    ]);

    $payroll->refresh();
    expect($payroll->bonus_amount)->toBe(800000);
    expect($payroll->deduction_amount)->toBe(100000);
    expect($payroll->total_amount)->toBe(3700000);
});

test('adjustment cannot be added to paid payroll', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::PAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 500000,
        'description' => 'Bonus',
    ]);

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

test('adjustment cannot be added to cancelled payroll', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::CANCELLED,
    ]);

    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 500000,
        'description' => 'Bonus',
    ]);

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

test('adjustment type is required', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => '',
        'amount' => 500000,
        'description' => 'Test',
    ]);

    $response->assertSessionHasErrors('type');
});

test('adjustment type must be valid', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'invalid_type',
        'amount' => 500000,
        'description' => 'Test',
    ]);

    $response->assertSessionHasErrors('type');
});

test('adjustment amount is required', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => '',
        'description' => 'Test',
    ]);

    $response->assertSessionHasErrors('amount');
});

test('adjustment amount must be at least 1', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 0,
        'description' => 'Test',
    ]);

    $response->assertSessionHasErrors('amount');
});

test('adjustment description is required', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 500000,
        'description' => '',
    ]);

    $response->assertSessionHasErrors('description');
});

test('adjustment description max length is 100', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 500000,
        'description' => str_repeat('a', 101),
    ]);

    $response->assertSessionHasErrors('description');
});

test('adjustment description exactly 100 characters is accepted', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::UNPAID,
    ]);

    $response = $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 500000,
        'description' => str_repeat('a', 100),
    ]);

    $response->assertSessionHasNoErrors();
});

/*
|--------------------------------------------------------------------------
| PAYROLL ADJUSTMENT - DELETE
|--------------------------------------------------------------------------
*/

test('adjustment can be deleted from unpaid payroll', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create(['base_salary' => 3000000]);
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'base_salary' => 3000000,
        'bonus_amount' => 500000,
        'deduction_amount' => 0,
        'total_amount' => 3500000,
        'status' => PayrollStatus::UNPAID,
    ]);

    $adjustment = PayrollAdjustment::factory()->create([
        'payroll_id' => $payroll->id,
        'created_by_id' => $user->id,
        'type' => PayrollAdjustmentType::BONUS,
        'amount' => 500000,
        'description' => 'Bonus performa',
    ]);

    $response = $this->delete(route('admin.finance.payroll-adjustments.destroy', $adjustment));

    $response
        ->assertSessionHasNoErrors()
        ->assertRedirect(route('admin.finance.payroll-periods.show', $period));

    $this->assertSoftDeleted('payroll_adjustments', ['id' => $adjustment->id]);

    $payroll->refresh();
    expect($payroll->bonus_amount)->toBe(0);
    expect($payroll->total_amount)->toBe(3000000);
});

test('deleting deduction adjustment recalculates correctly', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create(['base_salary' => 3000000]);
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'base_salary' => 3000000,
        'bonus_amount' => 0,
        'deduction_amount' => 200000,
        'total_amount' => 2800000,
        'status' => PayrollStatus::UNPAID,
    ]);

    $adjustment = PayrollAdjustment::factory()->create([
        'payroll_id' => $payroll->id,
        'created_by_id' => $user->id,
        'type' => PayrollAdjustmentType::DEDUCTION,
        'amount' => 200000,
        'description' => 'Potongan telat',
    ]);

    $this->delete(route('admin.finance.payroll-adjustments.destroy', $adjustment));

    $payroll->refresh();
    expect($payroll->deduction_amount)->toBe(0);
    expect($payroll->total_amount)->toBe(3000000);
});

test('adjustment cannot be deleted from paid payroll', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $period = PayrollPeriod::factory()->create(['status' => PayrollPeriodStatus::OPEN]);
    $employee = Employee::factory()->create();
    $payroll = Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
        'status' => PayrollStatus::PAID,
    ]);

    $adjustment = PayrollAdjustment::factory()->create([
        'payroll_id' => $payroll->id,
        'created_by_id' => $user->id,
        'type' => PayrollAdjustmentType::BONUS,
        'amount' => 500000,
        'description' => 'Test',
    ]);

    $response = $this->delete(route('admin.finance.payroll-adjustments.destroy', $adjustment));
    $response->assertRedirect();

    $this->assertDatabaseHas('payroll_adjustments', ['id' => $adjustment->id, 'deleted_at' => null]);
});

test('deleting non-existent adjustment returns 404', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $response = $this->delete(route('admin.finance.payroll-adjustments.destroy', 999999));
    $response->assertStatus(404);
});

/*
|--------------------------------------------------------------------------
| GENERATE PAYROLL COMMAND
|--------------------------------------------------------------------------
*/

test('generate payroll creates period and payrolls for active employees', function () {
    Employee::factory()->count(3)->create([
        'base_salary' => 3000000,
        'resign_date' => null,
    ]);

    $this->artisan('payroll:generate')
        ->expectsOutputToContain('Berhasil generate 3 gaji')
        ->assertSuccessful();

    $now = now();
    $this->assertDatabaseHas('payroll_periods', [
        'year' => $now->year,
        'month' => $now->month,
        'status' => PayrollPeriodStatus::OPEN->value,
    ]);

    $period = PayrollPeriod::where('year', $now->year)->where('month', $now->month)->first();
    $this->assertDatabaseCount('payrolls', 3);

    $payrolls = Payroll::where('payroll_period_id', $period->id)->get();
    expect($payrolls->every(fn ($p) => $p->status === PayrollStatus::UNPAID))->toBeTrue();
    expect($payrolls->every(fn ($p) => $p->base_salary === 3000000))->toBeTrue();
    expect($payrolls->every(fn ($p) => $p->total_amount === 3000000))->toBeTrue();
});

test('generate payroll does not duplicate existing payrolls', function () {
    $now = now();
    $period = PayrollPeriod::factory()->create([
        'year' => $now->year,
        'month' => $now->month,
        'status' => PayrollPeriodStatus::OPEN,
    ]);

    $employee = Employee::factory()->create(['base_salary' => 3000000]);
    Payroll::factory()->create([
        'payroll_period_id' => $period->id,
        'employee_id' => $employee->id,
    ]);

    $this->artisan('payroll:generate')
        ->expectsOutputToContain('Berhasil generate 0 gaji')
        ->assertSuccessful();

    $this->assertDatabaseCount('payrolls', 1);
});

test('generate payroll excludes resigned employees', function () {
    Employee::factory()->create([
        'base_salary' => 3000000,
        'resign_date' => now()->subMonth()->toDateString(),
    ]);

    $this->artisan('payroll:generate')->assertSuccessful();

    $this->assertDatabaseCount('payrolls', 0);
});

test('generate payroll uses employee base salary', function () {
    Employee::factory()->create(['base_salary' => 3500000]);
    Employee::factory()->create(['base_salary' => 4000000]);

    $this->artisan('payroll:generate')
        ->expectsOutputToContain('Berhasil generate 2 gaji')
        ->assertSuccessful();

    $this->assertDatabaseHas('payrolls', ['base_salary' => 3500000, 'total_amount' => 3500000]);
    $this->assertDatabaseHas('payrolls', ['base_salary' => 4000000, 'total_amount' => 4000000]);
});

test('generate payroll creates period with open status', function () {
    Employee::factory()->create();

    $this->artisan('payroll:generate')->assertSuccessful();

    $now = now();
    $period = PayrollPeriod::where('year', $now->year)->where('month', $now->month)->first();
    expect($period->status)->toBe(PayrollPeriodStatus::OPEN);
});

test('generate payroll does not change closed period', function () {
    $now = now();
    PayrollPeriod::factory()->create([
        'year' => $now->year,
        'month' => $now->month,
        'status' => PayrollPeriodStatus::CLOSED,
    ]);

    Employee::factory()->create();

    $this->artisan('payroll:generate')
        ->expectsOutputToContain('sudah ditutup')
        ->assertExitCode(1);
});

/*
|--------------------------------------------------------------------------
| MODEL & RELATIONSHIPS
|--------------------------------------------------------------------------
*/

test('payroll factory creates valid payroll', function () {
    $payroll = Payroll::factory()->create();

    expect($payroll->employee)->not->toBeNull();
    expect($payroll->payrollPeriod)->not->toBeNull();
    expect($payroll->base_salary)->toBeGreaterThan(0);
    expect($payroll->total_amount)->toBeGreaterThanOrEqual(0);
    expect($payroll->status)->toBeInstanceOf(PayrollStatus::class);
});

test('payroll has correct relationships', function () {
    $payroll = Payroll::factory()->create();

    expect($payroll->employee)->not->toBeNull();
    expect($payroll->payrollPeriod)->not->toBeNull();
    expect($payroll->payrollAdjustments)->not->toBeNull();
});

test('payroll period factory creates valid payroll period', function () {
    $period = PayrollPeriod::factory()->create();

    expect($period->year)->toBeGreaterThan(0);
    expect($period->month)->toBeGreaterThanOrEqual(1);
    expect($period->month)->toBeLessThanOrEqual(12);
    expect($period->status)->toBeInstanceOf(PayrollPeriodStatus::class);
});

test('payroll adjustment factory creates valid adjustment', function () {
    $adjustment = PayrollAdjustment::factory()->create();

    expect($adjustment->payroll)->not->toBeNull();
    expect($adjustment->createdBy)->not->toBeNull();
    expect($adjustment->amount)->toBeGreaterThan(0);
    expect($adjustment->type)->toBeInstanceOf(PayrollAdjustmentType::class);
});

/*
|--------------------------------------------------------------------------
| REALISTIC USER SCENARIOS
|--------------------------------------------------------------------------
*/

test('full payroll workflow: generate, adjust, pay', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    CashAccount::factory()->create(['balance' => 0]);

    $employee = Employee::factory()->create(['base_salary' => 3000000]);

    $this->artisan('payroll:generate')->assertSuccessful();

    $now = now();
    $period = PayrollPeriod::where('year', $now->year)->where('month', $now->month)->first();
    $payroll = Payroll::where('payroll_period_id', $period->id)->first();

    expect($payroll->status)->toBe(PayrollStatus::UNPAID);
    expect($payroll->total_amount)->toBe(3000000);

    $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 500000,
        'description' => 'Bonus performa',
    ]);

    $payroll->refresh();
    expect($payroll->bonus_amount)->toBe(500000);
    expect($payroll->total_amount)->toBe(3500000);

    $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'deduction',
        'amount' => 100000,
        'description' => 'Potongan telat',
    ]);

    $payroll->refresh();
    expect($payroll->deduction_amount)->toBe(100000);
    expect($payroll->total_amount)->toBe(3400000);

    $this->post(route('admin.finance.payrolls.pay', $payroll));

    $payroll->refresh();
    expect($payroll->status)->toBe(PayrollStatus::PAID);

    expect(CashAccount::first()->balance)->toBe(3400000);
});

test('full payroll workflow: generate, adjust, delete adjustment, pay', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    CashAccount::factory()->create(['balance' => 0]);

    $employee = Employee::factory()->create(['base_salary' => 3000000]);

    $this->artisan('payroll:generate')->assertSuccessful();

    $now = now();
    $period = PayrollPeriod::where('year', $now->year)->where('month', $now->month)->first();
    $payroll = Payroll::where('payroll_period_id', $period->id)->first();

    $this->post(route('admin.finance.payrolls.adjustments.store', $payroll), [
        'type' => 'bonus',
        'amount' => 500000,
        'description' => 'Bonus',
    ]);

    $adjustment = PayrollAdjustment::where('payroll_id', $payroll->id)->first();
    $this->delete(route('admin.finance.payroll-adjustments.destroy', $adjustment));

    $payroll->refresh();
    expect($payroll->bonus_amount)->toBe(0);
    expect($payroll->total_amount)->toBe(3000000);

    $this->post(route('admin.finance.payrolls.pay', $payroll));

    expect(CashAccount::first()->balance)->toBe(3000000);
});

test('full payroll workflow: generate, close period', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    CashAccount::factory()->create(['balance' => 0]);

    $employee = Employee::factory()->create(['base_salary' => 3000000]);

    $this->artisan('payroll:generate')->assertSuccessful();

    $now = now();
    $period = PayrollPeriod::where('year', $now->year)->where('month', $now->month)->first();
    $payroll = Payroll::where('payroll_period_id', $period->id)->first();

    $this->post(route('admin.finance.payrolls.pay', $payroll));

    $this->post(route('admin.finance.payroll-periods.close', $period));

    $period->refresh();
    expect($period->status)->toBe(PayrollPeriodStatus::CLOSED);

    $this->post(route('admin.finance.payroll-periods.reopen', $period));

    $period->refresh();
    expect($period->status)->toBe(PayrollPeriodStatus::OPEN);
});
