<?php

use App\Enums\LeaveRequestStatus;
use App\Enums\Permission as PermissionEnum;
use App\Models\Employee;
use App\Models\LeaveRequest;
use App\Models\User;
use App\Models\UserProfile;
use Carbon\CarbonInterface;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;

uses(RefreshDatabase::class);

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

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

function createLeaveRequestUserWithPermission(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 createLeaveEmployeeUser(): User
{
    $user = User::factory()->create();
    UserProfile::factory()->create([
        'user_id' => $user->id,
        'full_name' => fake()->name(),
    ]);
    Employee::factory()->create(['user_id' => $user->id]);
    $user->assignRole('marketing');
    $user->forgetCachedPermissions();

    return $user;
}

function createEmployeeUserWithLeavePermission(PermissionEnum ...$permissions): User
{
    $user = createLeaveEmployeeUser();

    $user->givePermissionTo(
        array_merge(
            [PermissionEnum::DASHBOARD_VIEW->value],
            array_map(fn (PermissionEnum $p) => $p->value, $permissions)
        )
    );

    $user->forgetCachedPermissions();

    return $user;
}

function leaveRequestPayload(): array
{
    return [
        'start_date' => now()->addDays(2)->format('Y-m-d'),
        'end_date' => now()->addDays(4)->format('Y-m-d'),
    ];
}

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

describe('Leave Request Index', function () {
    test('authenticated user with permission can view leave request index', function () {
        $user = createLeaveRequestUserWithPermission(PermissionEnum::LEAVE_REQUESTS_VIEW);

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

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

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

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

    test('index displays leave requests for manager role', function () {
        $user = createLeaveRequestUserWithPermission(PermissionEnum::LEAVE_REQUESTS_VIEW);

        LeaveRequest::factory()->count(3)->create();

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

    test('employee only sees own leave requests', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_CREATE,
        );

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

        $otherRequest = LeaveRequest::factory()->create();

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

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

describe('Leave Request Store', function () {
    test('employee with permission can submit leave request', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_CREATE,
        );

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.store'), leaveRequestPayload())
            ->assertRedirect(route('admin.hr.leave_requests.index'));

        $this->assertDatabaseHas('leave_requests', [
            'employee_id' => $user->employee->id,
            'status' => LeaveRequestStatus::PENDING->value,
        ]);
    });

    test('guest cannot submit leave request', function () {
        $this->post(route('admin.hr.leave_requests.store'), leaveRequestPayload())
            ->assertRedirect(route('login'));
    });

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

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.store'), leaveRequestPayload())
            ->assertForbidden();
    });

    test('user without employee record cannot submit leave request', function () {
        $user = createLeaveRequestUserWithPermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_CREATE,
        );

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.store'), leaveRequestPayload())
            ->assertForbidden();
    });

    test('start_date is required', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_CREATE,
        );

        $payload = leaveRequestPayload();
        unset($payload['start_date']);

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.store'), $payload)
            ->assertSessionHasErrors('start_date');
    });

    test('end_date is required', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_CREATE,
        );

        $payload = leaveRequestPayload();
        unset($payload['end_date']);

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.store'), $payload)
            ->assertSessionHasErrors('end_date');
    });

    test('end_date must be after or equal to start_date', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_CREATE,
        );

        $payload = [
            'start_date' => now()->addDays(5)->format('Y-m-d'),
            'end_date' => now()->addDays(2)->format('Y-m-d'),
        ];

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.store'), $payload)
            ->assertSessionHasErrors('end_date');
    });

    test('cannot submit when there is a pending leave request', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_CREATE,
        );

        LeaveRequest::factory()->create([
            'employee_id' => $user->employee->id,
            'status' => LeaveRequestStatus::PENDING,
        ]);

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.store'), leaveRequestPayload())
            ->assertSessionHasErrors('start_date');
    });

    test('submitting leave request calculates total_days correctly', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_CREATE,
        );

        $payload = [
            'start_date' => now()->addDays(2)->format('Y-m-d'),
            'end_date' => now()->addDays(5)->format('Y-m-d'),
        ];

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.store'), $payload);

        $leaveRequest = LeaveRequest::where('employee_id', $user->employee->id)->first();
        expect($leaveRequest->total_days)->toBe(4);
    });

    test('submitting leave request creates with pending status', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_CREATE,
        );

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.store'), leaveRequestPayload());

        $leaveRequest = LeaveRequest::where('employee_id', $user->employee->id)->first();
        expect($leaveRequest->status)->toBe(LeaveRequestStatus::PENDING);
        expect($leaveRequest->verified_at)->toBeNull();
        expect($leaveRequest->verified_by_id)->toBeNull();
    });
});

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

describe('Leave Request Update', function () {
    test('employee with permission can update own pending leave request', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_UPDATE,
        );

        $leaveRequest = LeaveRequest::factory()->create([
            'employee_id' => $user->employee->id,
            'status' => LeaveRequestStatus::PENDING,
        ]);

        $payload = [
            'start_date' => now()->addDays(10)->format('Y-m-d'),
            'end_date' => now()->addDays(12)->format('Y-m-d'),
        ];

        $this->actingAs($user)
            ->put(route('admin.hr.leave_requests.update', $leaveRequest), $payload)
            ->assertRedirect(route('admin.hr.leave_requests.index'));

        expect($leaveRequest->fresh()->total_days)->toBe(3);
    });

    test('guest cannot update leave request', function () {
        $leaveRequest = LeaveRequest::factory()->create();

        $this->put(route('admin.hr.leave_requests.update', $leaveRequest), leaveRequestPayload())
            ->assertRedirect(route('login'));
    });

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

        $leaveRequest = LeaveRequest::factory()->create([
            'employee_id' => Employee::factory()->create()->id,
            'status' => LeaveRequestStatus::PENDING,
        ]);

        $this->actingAs($user)
            ->put(route('admin.hr.leave_requests.update', $leaveRequest), leaveRequestPayload())
            ->assertForbidden();
    });

    test('employee cannot update other employee leave request', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_UPDATE,
        );

        $otherLeaveRequest = LeaveRequest::factory()->create([
            'status' => LeaveRequestStatus::PENDING,
        ]);

        $this->actingAs($user)
            ->put(route('admin.hr.leave_requests.update', $otherLeaveRequest), leaveRequestPayload())
            ->assertForbidden();
    });

    test('cannot update approved leave request', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_UPDATE,
        );

        $leaveRequest = LeaveRequest::factory()->create([
            'employee_id' => $user->employee->id,
            'status' => LeaveRequestStatus::APPROVED,
        ]);

        $this->actingAs($user)
            ->put(route('admin.hr.leave_requests.update', $leaveRequest), leaveRequestPayload())
            ->assertForbidden();
    });
});

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

describe('Leave Request Destroy', function () {
    test('employee with permission can delete own leave request', function () {
        $user = createEmployeeUserWithLeavePermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_DELETE,
        );

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

        $this->actingAs($user)
            ->delete(route('admin.hr.leave_requests.destroy', $leaveRequest))
            ->assertRedirect(route('admin.hr.leave_requests.index'));

        $this->assertDatabaseMissing('leave_requests', ['id' => $leaveRequest->id]);
    });

    test('guest cannot delete leave request', function () {
        $leaveRequest = LeaveRequest::factory()->create();

        $this->delete(route('admin.hr.leave_requests.destroy', $leaveRequest))
            ->assertRedirect(route('login'));
    });

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

        $leaveRequest = LeaveRequest::factory()->create();

        $this->actingAs($user)
            ->delete(route('admin.hr.leave_requests.destroy', $leaveRequest))
            ->assertForbidden();
    });
});

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

describe('Leave Request Approve', function () {
    test('user with verify permission can approve leave request', function () {
        $user = createLeaveRequestUserWithPermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_VERIFY,
        );

        $leaveRequest = LeaveRequest::factory()->create([
            'status' => LeaveRequestStatus::PENDING,
        ]);

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.approve', $leaveRequest))
            ->assertRedirect(route('admin.hr.leave_requests.index'));

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

    test('guest cannot approve leave request', function () {
        $leaveRequest = LeaveRequest::factory()->create();

        $this->post(route('admin.hr.leave_requests.approve', $leaveRequest))
            ->assertRedirect(route('login'));
    });

    test('user without verify permission is forbidden', function () {
        $user = createLeaveRequestUserWithPermission(PermissionEnum::LEAVE_REQUESTS_VIEW);

        $leaveRequest = LeaveRequest::factory()->create([
            'status' => LeaveRequestStatus::PENDING,
        ]);

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.approve', $leaveRequest))
            ->assertForbidden();
    });
});

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

describe('Leave Request Reject', function () {
    test('user with verify permission can reject leave request', function () {
        $user = createLeaveRequestUserWithPermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_VERIFY,
        );

        $leaveRequest = LeaveRequest::factory()->create([
            'status' => LeaveRequestStatus::PENDING,
        ]);

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.reject', $leaveRequest), [
                'reason' => 'Tidak ada pengganti',
            ])
            ->assertRedirect(route('admin.hr.leave_requests.index'));

        expect($leaveRequest->fresh()->status)->toBe(LeaveRequestStatus::REJECTED);
        expect($leaveRequest->fresh()->verified_by_id)->toBe($user->id);
        expect($leaveRequest->fresh()->verified_at)->not->toBeNull();
    });

    test('rejecting creates rejection record', function () {
        $user = createLeaveRequestUserWithPermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_VERIFY,
        );

        $leaveRequest = LeaveRequest::factory()->create([
            'status' => LeaveRequestStatus::PENDING,
        ]);

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.reject', $leaveRequest), [
                'reason' => 'Stok karyawan kurang',
            ]);

        $this->assertDatabaseHas('rejections', [
            'rejectable_type' => LeaveRequest::class,
            'rejectable_id' => $leaveRequest->id,
            'reason' => 'Stok karyawan kurang',
            'rejected_by_id' => $user->id,
        ]);
    });

    test('guest cannot reject leave request', function () {
        $leaveRequest = LeaveRequest::factory()->create();

        $this->post(route('admin.hr.leave_requests.reject', $leaveRequest), [
            'reason' => 'Test',
        ])->assertRedirect(route('login'));
    });

    test('user without verify permission is forbidden', function () {
        $user = createLeaveRequestUserWithPermission(PermissionEnum::LEAVE_REQUESTS_VIEW);

        $leaveRequest = LeaveRequest::factory()->create([
            'status' => LeaveRequestStatus::PENDING,
        ]);

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.reject', $leaveRequest), [
                'reason' => 'Test',
            ])
            ->assertForbidden();
    });

    test('reason is required when rejecting', function () {
        $user = createLeaveRequestUserWithPermission(
            PermissionEnum::LEAVE_REQUESTS_VIEW,
            PermissionEnum::LEAVE_REQUESTS_VERIFY,
        );

        $leaveRequest = LeaveRequest::factory()->create([
            'status' => LeaveRequestStatus::PENDING,
        ]);

        $this->actingAs($user)
            ->post(route('admin.hr.leave_requests.reject', $leaveRequest), [
                'reason' => '',
            ])
            ->assertSessionHasErrors('reason');
    });
});

// ─── Leave Request Model ──────────────────────────────────

describe('Leave Request Model', function () {
    test('leave request has correct casts', function () {
        $leaveRequest = LeaveRequest::factory()->create([
            'total_days' => 5,
        ]);

        expect($leaveRequest->total_days)->toBeInt();
        expect($leaveRequest->start_date)->toBeInstanceOf(CarbonInterface::class);
        expect($leaveRequest->end_date)->toBeInstanceOf(CarbonInterface::class);
    });

    test('leave request belongs to employee', function () {
        $leaveRequest = LeaveRequest::factory()->create();

        expect($leaveRequest->employee)->not->toBeNull();
        expect($leaveRequest->employee)->toBeInstanceOf(Employee::class);
    });

    test('leave request has status_label accessor', function () {
        $leaveRequest = LeaveRequest::factory()->create([
            'status' => LeaveRequestStatus::PENDING,
        ]);

        expect($leaveRequest->status_label)->toBe('Menunggu');

        $leaveRequest->update(['status' => LeaveRequestStatus::APPROVED]);
        expect($leaveRequest->fresh()->status_label)->toBe('Disetujui');

        $leaveRequest->update(['status' => LeaveRequestStatus::REJECTED]);
        expect($leaveRequest->fresh()->status_label)->toBe('Ditolak');
    });

    test('leave request has start_date_formatted accessor', function () {
        $leaveRequest = LeaveRequest::factory()->create([
            'start_date' => '2025-06-15',
        ]);

        expect($leaveRequest->start_date_formatted)->toBeString();
    });

    test('leave request has end_date_formatted accessor', function () {
        $leaveRequest = LeaveRequest::factory()->create([
            'end_date' => '2025-06-20',
        ]);

        expect($leaveRequest->end_date_formatted)->toBeString();
    });

    test('leave request has start_date_input accessor', function () {
        $leaveRequest = LeaveRequest::factory()->create([
            'start_date' => '2025-06-15',
        ]);

        expect($leaveRequest->start_date_input)->toBe('2025-06-15');
    });

    test('leave request has end_date_input accessor', function () {
        $leaveRequest = LeaveRequest::factory()->create([
            'end_date' => '2025-06-20',
        ]);

        expect($leaveRequest->end_date_input)->toBe('2025-06-20');
    });

    test('leave request has is_editable accessor', function () {
        $leaveRequest = LeaveRequest::factory()->create([
            'status' => LeaveRequestStatus::PENDING,
        ]);

        expect($leaveRequest->is_editable)->toBeTrue();

        $leaveRequest->update(['status' => LeaveRequestStatus::APPROVED]);
        expect($leaveRequest->fresh()->is_editable)->toBeFalse();
    });

    test('leave request has can_verify accessor', function () {
        $leaveRequest = LeaveRequest::factory()->create([
            'status' => LeaveRequestStatus::PENDING,
        ]);

        expect($leaveRequest->can_verify)->toBeTrue();

        $leaveRequest->update(['status' => LeaveRequestStatus::APPROVED]);
        expect($leaveRequest->fresh()->can_verify)->toBeFalse();
    });

    test('leave request has employee_name accessor', function () {
        $leaveRequest = LeaveRequest::factory()->create();

        expect($leaveRequest->employee_name)->toBeString();
    });

    test('pending scope filters correctly', function () {
        LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]);
        LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]);

        expect(LeaveRequest::query()->pending()->count())->toBe(1);
    });

    test('approved scope filters correctly', function () {
        LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]);
        LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]);

        expect(LeaveRequest::query()->approved()->count())->toBe(1);
    });
});
