<?php

use App\Enums\Permission as PermissionEnum;
use App\Models\Attendance;
use App\Models\Employee;
use App\Models\User;
use App\Models\UserProfile;
use Carbon\CarbonInterface;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;

uses(RefreshDatabase::class);

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

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

function createAttendanceUserWithPermission(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 createAttendanceEmployeeUser(PermissionEnum ...$permissions): 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->givePermissionTo(
        array_merge(
            [PermissionEnum::DASHBOARD_VIEW->value],
            array_map(fn (PermissionEnum $p) => $p->value, $permissions)
        )
    );

    $user->forgetCachedPermissions();

    return $user;
}

function fakeBase64Photo(): string
{
    // 1x1 pixel transparent PNG
    return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
}

function checkInPayload(): array
{
    return [
        'photo' => fakeBase64Photo(),
        'latitude' => -6.2088,
        'longitude' => 106.8456,
    ];
}

function checkOutPayload(): array
{
    return [
        'photo' => fakeBase64Photo(),
        'latitude' => -6.2090,
        'longitude' => 106.8460,
    ];
}

function createTodayAttendanceForEmployee(Employee $employee): Attendance
{
    return Attendance::factory()->create([
        'employee_id' => $employee->id,
        'attendance_date' => today()->format('Y-m-d'),
        'check_in_at' => now()->subHours(8),
        'check_out_at' => null,
        'check_out_latitude' => null,
        'check_out_longitude' => null,
        'work_duration_minutes' => null,
    ]);
}

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

describe('Attendance Index', function () {
    test('authenticated user with permission can view attendance index', function () {
        $user = createAttendanceUserWithPermission(PermissionEnum::ATTENDANCES_VIEW);

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

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

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

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

    test('index displays attendances for employee user', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW);

        Attendance::factory()->count(3)->create([
            'employee_id' => $user->employee->id,
        ]);

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

    test('manager can see all attendances', function () {
        $user = createAttendanceUserWithPermission(PermissionEnum::ATTENDANCES_VIEW);
        $user->assignRole('owner');
        $user->forgetCachedPermissions();

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

    test('index accepts date range parameters', function () {
        $user = createAttendanceUserWithPermission(PermissionEnum::ATTENDANCES_VIEW);

        $this->actingAs($user)
            ->get(route('admin.hr.attendances.index', [
                'start' => now()->startOfMonth()->format('Y-m-d'),
                'end' => now()->endOfMonth()->format('Y-m-d'),
            ]))
            ->assertOk();
    });
});

// ─── Check In ─────────────────────────────────────────────

describe('Attendance Check In', function () {
    test('employee with permission can check in', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_in'), checkInPayload())
            ->assertRedirect(route('admin.hr.attendances.index'));

        $this->assertDatabaseHas('attendances', [
            'employee_id' => $user->employee->id,
            'attendance_date' => today()->format('Y-m-d'),
        ]);
    });

    test('guest cannot check in', function () {
        $this->post(route('admin.hr.attendances.check_in'), checkInPayload())
            ->assertRedirect(route('login'));
    });

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

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_in'), checkInPayload())
            ->assertForbidden();
    });

    test('user without employee record cannot check in', function () {
        $user = createAttendanceUserWithPermission(
            PermissionEnum::ATTENDANCES_VIEW,
            PermissionEnum::ATTENDANCES_CREATE,
        );

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_in'), checkInPayload())
            ->assertStatus(302);
    });

    test('cannot check in twice on the same day', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        createTodayAttendanceForEmployee($user->employee);

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_in'), checkInPayload())
            ->assertSessionHasErrors('attendance');
    });

    test('photo is required for check in', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        $payload = checkInPayload();
        $payload['photo'] = '';

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_in'), $payload)
            ->assertSessionHasErrors('photo');
    });

    test('latitude is required for check in', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        $payload = checkInPayload();
        unset($payload['latitude']);

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_in'), $payload)
            ->assertSessionHasErrors('latitude');
    });

    test('longitude is required for check in', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        $payload = checkInPayload();
        unset($payload['longitude']);

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_in'), $payload)
            ->assertSessionHasErrors('longitude');
    });

    test('latitude must be between -90 and 90', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        $payload = checkInPayload();
        $payload['latitude'] = 100;

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_in'), $payload)
            ->assertSessionHasErrors('latitude');
    });

    test('longitude must be between -180 and 180', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        $payload = checkInPayload();
        $payload['longitude'] = 200;

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_in'), $payload)
            ->assertSessionHasErrors('longitude');
    });

    test('check in stores latitude and longitude', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_in'), [
                'photo' => fakeBase64Photo(),
                'latitude' => -6.2088,
                'longitude' => 106.8456,
            ]);

        $attendance = Attendance::where('employee_id', $user->employee->id)->first();
        expect($attendance)->not->toBeNull();
        expect($attendance->check_in_at)->not->toBeNull();
        expect($attendance->check_out_at)->toBeNull();
    });
});

// ─── Check Out ────────────────────────────────────────────

describe('Attendance Check Out', function () {
    test('employee with permission can check out', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        createTodayAttendanceForEmployee($user->employee);

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_out'), checkOutPayload())
            ->assertRedirect(route('admin.hr.attendances.index'));

        $attendance = Attendance::where('employee_id', $user->employee->id)
            ->whereDate('attendance_date', today())
            ->first();

        expect($attendance->check_out_at)->not->toBeNull();
        expect($attendance->work_duration_minutes)->not->toBeNull();
    });

    test('guest cannot check out', function () {
        $this->post(route('admin.hr.attendances.check_out'), checkOutPayload())
            ->assertRedirect(route('login'));
    });

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

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_out'), checkOutPayload())
            ->assertForbidden();
    });

    test('cannot check out without checking in first', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_out'), checkOutPayload())
            ->assertSessionHasErrors('attendance');
    });

    test('cannot check out twice on the same day', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        $attendance = createTodayAttendanceForEmployee($user->employee);
        $attendance->update([
            'check_out_at' => now(),
            'work_duration_minutes' => 480,
        ]);

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_out'), checkOutPayload())
            ->assertSessionHasErrors('attendance');
    });

    test('photo is required for check out', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        createTodayAttendanceForEmployee($user->employee);

        $payload = checkOutPayload();
        $payload['photo'] = '';

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_out'), $payload)
            ->assertSessionHasErrors('photo');
    });

    test('check out calculates work duration', function () {
        $user = createAttendanceEmployeeUser(PermissionEnum::ATTENDANCES_VIEW, PermissionEnum::ATTENDANCES_CREATE);

        Attendance::factory()->create([
            'employee_id' => $user->employee->id,
            'attendance_date' => today()->format('Y-m-d'),
            'check_in_at' => now()->subHours(8),
            'check_out_at' => null,
            'check_out_latitude' => null,
            'check_out_longitude' => null,
            'work_duration_minutes' => null,
        ]);

        $this->actingAs($user)
            ->post(route('admin.hr.attendances.check_out'), checkOutPayload());

        $attendance = Attendance::where('employee_id', $user->employee->id)
            ->whereDate('attendance_date', today())
            ->first();

        expect($attendance->work_duration_minutes)->toBeGreaterThan(0);
    });
});

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

describe('Attendance Destroy', function () {
    test('user with permission can delete attendance', function () {
        $user = createAttendanceUserWithPermission(
            PermissionEnum::ATTENDANCES_VIEW,
            PermissionEnum::ATTENDANCES_DELETE,
        );

        $attendance = Attendance::factory()->create();

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

        $this->assertDatabaseMissing('attendances', ['id' => $attendance->id]);
    });

    test('guest cannot delete attendance', function () {
        $attendance = Attendance::factory()->create();

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

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

        $attendance = Attendance::factory()->create();

        $this->actingAs($user)
            ->delete(route('admin.hr.attendances.destroy', $attendance))
            ->assertForbidden();

        $this->assertDatabaseHas('attendances', ['id' => $attendance->id]);
    });
});

// ─── Attendance Model ─────────────────────────────────────

describe('Attendance Model', function () {
    test('attendance has correct casts', function () {
        $attendance = Attendance::factory()->create([
            'check_in_latitude' => -6.2088000,
            'check_in_longitude' => 106.8456000,
        ]);

        expect($attendance->attendance_date)->toBeInstanceOf(CarbonInterface::class);
        expect($attendance->check_in_at)->toBeInstanceOf(CarbonInterface::class);
        expect($attendance->check_out_at)->toBeInstanceOf(CarbonInterface::class);
        expect($attendance->work_duration_minutes)->toBeInt();
    });

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

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

    test('attendance has check_in_at_formatted accessor', function () {
        $attendance = Attendance::factory()->create([
            'check_in_at' => '2025-06-15 08:00:00',
        ]);

        expect($attendance->check_in_at_formatted)->toBeString();
    });

    test('attendance has check_out_at_formatted accessor', function () {
        $attendance = Attendance::factory()->create([
            'check_out_at' => '2025-06-15 17:00:00',
        ]);

        expect($attendance->check_out_at_formatted)->toBeString();
    });

    test('attendance has attendance_date_formatted accessor', function () {
        $attendance = Attendance::factory()->create([
            'attendance_date' => '2025-06-15',
        ]);

        expect($attendance->attendance_date_formatted)->toBeString();
    });

    test('attendance has employee_name accessor', function () {
        $employee = Employee::factory()->create();
        UserProfile::factory()->create([
            'user_id' => $employee->user_id,
            'full_name' => 'John Doe',
        ]);

        $attendance = Attendance::factory()->create([
            'employee_id' => $employee->id,
        ]);

        expect($attendance->employee_name)->toBe('John Doe');
    });

    test('attendance has work_duration_formatted accessor with hours', function () {
        $attendance = Attendance::factory()->create([
            'work_duration_minutes' => 480,
        ]);

        expect($attendance->work_duration_formatted)->toBe('8 jam 0 menit');
    });

    test('attendance has work_duration_formatted accessor with minutes only', function () {
        $attendance = Attendance::factory()->create([
            'work_duration_minutes' => 45,
        ]);

        expect($attendance->work_duration_formatted)->toBe('45 menit');
    });

    test('attendance has null work_duration_formatted when no checkout', function () {
        $attendance = Attendance::factory()->create([
            'work_duration_minutes' => null,
        ]);

        expect($attendance->work_duration_formatted)->toBeNull();
    });

    test('checkedInOnly factory state works', function () {
        $attendance = Attendance::factory()->checkedInOnly()->create();

        expect($attendance->check_out_at)->toBeNull();
        expect($attendance->check_out_latitude)->toBeNull();
        expect($attendance->check_out_longitude)->toBeNull();
        expect($attendance->work_duration_minutes)->toBeNull();
    });

    test('attendance has payroll_adjustments relationship', function () {
        $attendance = Attendance::factory()->create();

        expect($attendance->payrollAdjustments())->toBeInstanceOf(HasMany::class);
    });
});
