<?php

namespace Tests\Feature;

use App\Models\Attendance;
use App\Models\AttendanceRegularization;
use App\Models\User;
use Carbon\Carbon;
use Tests\TestCase;

/**
 * Tests for attendance regularization approval functionality.
 *
 * These tests verify that when a regularization request is approved:
 * 1. Attendance record is created (if it doesn't exist) or updated
 * 2. Check-in/check-out times are set based on regularization type
 * 3. Attendance status is updated correctly
 * 4. Working hours are recalculated
 * 5. Attendance logs are created for audit trail
 */
class AttendanceRegularizationApprovalTest extends TestCase
{
    private User $manager;

    private User $employee;

    protected function setUp(): void
    {
        parent::setUp();

        // Create manager (who will approve) and employee (who submitted request)
        $this->manager = User::factory()->create();
        $this->employee = User::factory()->create();
    }

    /**
     * Test approving a regularization when no attendance record exists.
     * Should create a new attendance record.
     */
    public function test_approve_creates_attendance_when_none_exists(): void
    {
        $regularizationDate = Carbon::now()->subDays(5)->format('Y-m-d');

        // Create regularization request without attendance
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->missingCheckin()
            ->create([
                'requested_check_in_time' => '09:30',
            ]);

        // Verify no attendance exists initially
        $this->assertDatabaseMissing('attendances', [
            'user_id' => $this->employee->id,
            'date' => $regularizationDate,
        ]);

        // Approve the regularization
        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id), [
                'manager_comments' => 'Approved',
            ])
            ->assertJson(['status' => 'success']);

        // Verify attendance was created
        $this->assertDatabaseHas('attendances', [
            'user_id' => $this->employee->id,
            'date' => $regularizationDate,
            'status' => Attendance::STATUS_CHECKED_IN,
        ]);

        // Verify check-in time was set
        $attendance = Attendance::where('user_id', $this->employee->id)
            ->whereDate('date', $regularizationDate)
            ->first();

        $this->assertNotNull($attendance);
        $this->assertNotNull($attendance->check_in_time);
        $this->assertEquals('09:30:00', Carbon::parse($attendance->check_in_time)->format('H:i:s'));
    }

    /**
     * Test approving missing_checkin type updates only check_in_time.
     */
    public function test_approve_missing_checkin_updates_only_check_in_time(): void
    {
        $regularizationDate = Carbon::now()->subDays(3)->format('Y-m-d');

        // Create attendance without check-in
        $attendance = Attendance::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->create([
                'check_out_time' => Carbon::parse($regularizationDate)->setTime(17, 0, 0),
                'status' => Attendance::STATUS_CHECKED_OUT,
            ]);

        // Create missing check-in regularization
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->missingCheckin()
            ->create([
                'attendance_id' => $attendance->id,
                'requested_check_in_time' => '08:45',
            ]);

        // Approve
        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id))
            ->assertJson(['status' => 'success']);

        // Verify only check-in was updated
        $attendance->refresh();
        $this->assertEquals('08:45:00', Carbon::parse($attendance->check_in_time)->format('H:i:s'));
        $this->assertEquals('17:00:00', Carbon::parse($attendance->check_out_time)->format('H:i:s'));
        $this->assertEquals(Attendance::STATUS_CHECKED_OUT, $attendance->status);
    }

    /**
     * Test approving missing_checkout type updates only check_out_time.
     */
    public function test_approve_missing_checkout_updates_only_check_out_time(): void
    {
        $regularizationDate = Carbon::now()->subDays(2)->format('Y-m-d');

        // Create attendance with check-in only
        $attendance = Attendance::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->checkedIn()
            ->create([
                'check_in_time' => Carbon::parse($regularizationDate)->setTime(9, 0, 0),
            ]);

        // Create missing check-out regularization
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->missingCheckout()
            ->create([
                'attendance_id' => $attendance->id,
                'requested_check_out_time' => '18:30',
            ]);

        // Approve
        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id))
            ->assertJson(['status' => 'success']);

        // Verify only check-out was updated
        $attendance->refresh();
        $this->assertEquals('09:00:00', Carbon::parse($attendance->check_in_time)->format('H:i:s'));
        $this->assertEquals('18:30:00', Carbon::parse($attendance->check_out_time)->format('H:i:s'));
        $this->assertEquals(Attendance::STATUS_CHECKED_OUT, $attendance->status);
    }

    /**
     * Test approving wrong_time type updates both times.
     */
    public function test_approve_wrong_time_updates_both_times(): void
    {
        $regularizationDate = Carbon::now()->subDays(4)->format('Y-m-d');

        // Create attendance with wrong times
        $attendance = Attendance::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->checkedOut()
            ->create();

        // Create wrong time regularization
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->wrongTime()
            ->create([
                'attendance_id' => $attendance->id,
                'requested_check_in_time' => '08:00',
                'requested_check_out_time' => '19:00',
            ]);

        // Approve
        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id))
            ->assertJson(['status' => 'success']);

        // Verify both times were updated
        $attendance->refresh();
        $this->assertEquals('08:00:00', Carbon::parse($attendance->check_in_time)->format('H:i:s'));
        $this->assertEquals('19:00:00', Carbon::parse($attendance->check_out_time)->format('H:i:s'));
        $this->assertEquals(Attendance::STATUS_CHECKED_OUT, $attendance->status);
    }

    /**
     * Test that working hours are recalculated after approval.
     */
    public function test_approve_recalculates_working_hours(): void
    {
        $regularizationDate = Carbon::now()->subDays(1)->format('Y-m-d');

        // Create regularization for forgot_punch (full day)
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->forgotPunch()
            ->create([
                'requested_check_in_time' => '09:00',
                'requested_check_out_time' => '17:00',
            ]);

        // Approve
        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id))
            ->assertJson(['status' => 'success']);

        // Verify working hours were calculated (8 hours = 9:00 to 17:00)
        $attendance = Attendance::where('user_id', $this->employee->id)
            ->whereDate('date', $regularizationDate)
            ->first();

        $this->assertNotNull($attendance);
        // Working hours should be around 8.0 (may vary slightly due to calculation logic)
        $this->assertGreaterThanOrEqual(0, $attendance->working_hours);
    }

    /**
     * Test that attendance logs are created for audit trail.
     */
    public function test_approve_creates_attendance_logs(): void
    {
        $regularizationDate = Carbon::now()->subDays(6)->format('Y-m-d');

        // Create regularization for forgot punch
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->forgotPunch()
            ->create([
                'requested_check_in_time' => '09:00',
                'requested_check_out_time' => '17:00',
            ]);

        // Approve
        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id))
            ->assertJson(['status' => 'success']);

        // Get the attendance
        $attendance = Attendance::where('user_id', $this->employee->id)
            ->whereDate('date', $regularizationDate)
            ->first();

        $this->assertNotNull($attendance);

        // Verify check-in log was created
        $this->assertDatabaseHas('attendance_logs', [
            'attendance_id' => $attendance->id,
            'type' => 'check_in',
        ]);

        // Verify check-out log was created
        $this->assertDatabaseHas('attendance_logs', [
            'attendance_id' => $attendance->id,
            'type' => 'check_out',
        ]);
    }

    /**
     * Test that attendance status is updated correctly when only check-in is set.
     */
    public function test_status_is_checked_in_when_only_check_in_time_set(): void
    {
        $regularizationDate = Carbon::now()->subDays(7)->format('Y-m-d');

        // Create missing check-in regularization
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->missingCheckin()
            ->create([
                'requested_check_in_time' => '09:00',
            ]);

        // Approve
        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id))
            ->assertJson(['status' => 'success']);

        // Verify status is checked_in (not checked_out)
        $attendance = Attendance::where('user_id', $this->employee->id)
            ->whereDate('date', $regularizationDate)
            ->first();

        $this->assertNotNull($attendance);
        $this->assertEquals(Attendance::STATUS_CHECKED_IN, $attendance->status);
        $this->assertNull($attendance->check_out_time);
    }

    /**
     * Test that attendance status is checked_out when both times are set.
     */
    public function test_status_is_checked_out_when_both_times_set(): void
    {
        $regularizationDate = Carbon::now()->subDays(8)->format('Y-m-d');

        // Create forgot punch regularization
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->forgotPunch()
            ->create([
                'requested_check_in_time' => '09:00',
                'requested_check_out_time' => '17:00',
            ]);

        // Approve
        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id))
            ->assertJson(['status' => 'success']);

        // Verify status is checked_out
        $attendance = Attendance::where('user_id', $this->employee->id)
            ->whereDate('date', $regularizationDate)
            ->first();

        $this->assertNotNull($attendance);
        $this->assertEquals(Attendance::STATUS_CHECKED_OUT, $attendance->status);
    }

    /**
     * Test that regularization is linked to attendance record.
     */
    public function test_regularization_is_linked_to_attendance(): void
    {
        $regularizationDate = Carbon::now()->subDays(9)->format('Y-m-d');

        // Create regularization without attendance
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->missingCheckin()
            ->create([
                'requested_check_in_time' => '09:00',
            ]);

        // Initially, regularization has no attendance_id
        $this->assertNull($regularization->attendance_id);

        // Approve
        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id))
            ->assertJson(['status' => 'success']);

        // Verify regularization is now linked to attendance
        $regularization->refresh();
        $this->assertNotNull($regularization->attendance_id);

        // Verify the link is correct
        $attendance = Attendance::where('user_id', $this->employee->id)
            ->whereDate('date', $regularizationDate)
            ->first();
        $this->assertEquals($attendance->id, $regularization->attendance_id);
    }

    /**
     * Test that already approved regularization cannot be approved again.
     */
    public function test_cannot_approve_already_approved_regularization(): void
    {
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->approved()
            ->create();

        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id))
            ->assertStatus(400);
    }

    /**
     * Test that rejected regularization cannot be approved.
     */
    public function test_cannot_approve_rejected_regularization(): void
    {
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->rejected()
            ->create();

        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id))
            ->assertStatus(400);
    }

    /**
     * Test approving regularization with existing attendance that has no times.
     */
    public function test_approve_updates_existing_attendance_without_times(): void
    {
        $regularizationDate = Carbon::now()->subDays(10)->format('Y-m-d');

        // Create attendance marked as absent (no times)
        $attendance = Attendance::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->create([
                'status' => Attendance::STATUS_ABSENT,
                'check_in_time' => null,
                'check_out_time' => null,
            ]);

        // Create regularization for forgot punch
        $regularization = AttendanceRegularization::factory()
            ->forUser($this->employee)
            ->forDate($regularizationDate)
            ->forgotPunch()
            ->create([
                'attendance_id' => $attendance->id,
                'requested_check_in_time' => '09:00',
                'requested_check_out_time' => '17:00',
            ]);

        // Approve
        $this->actingAs($this->manager)
            ->post(route('hrcore.attendance-regularization.approve', $regularization->id))
            ->assertJson(['status' => 'success']);

        // Verify attendance was updated
        $attendance->refresh();
        $this->assertNotNull($attendance->check_in_time);
        $this->assertNotNull($attendance->check_out_time);
        $this->assertEquals(Attendance::STATUS_CHECKED_OUT, $attendance->status);
    }
}
