<?php

namespace App\Jobs;

use App\Enums\PayrollAdjustmentType;
use App\Enums\PayrollStatus;
use App\Models\Attendance;
use App\Models\Employee;
use App\Models\Payroll;
use App\Models\PayrollAdjustment;
use App\Models\PayrollPeriod;
use App\Models\User;
use App\Settings\HRSettings;
use Carbon\CarbonInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;

class CheckAttendancePenaltiesJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;

    public function handle(): void
    {
        $yesterday = now()->subDay();
        $dayOfWeek = (int) $yesterday->format('w');

        if ($dayOfWeek === 0 || $dayOfWeek === 6) {
            return;
        }

        $hrSettings = app(HRSettings::class);

        if ($hrSettings->late_penalty_amount <= 0 && $hrSettings->absent_penalty_amount <= 0) {
            return;
        }

        $cuttingDay = 5;

        if ($yesterday->day < $cuttingDay) {
            $periodMonth = $yesterday->month;
            $periodYear = $yesterday->year;
        } else {
            $periodMonth = $yesterday->month + 1;
            $periodYear = $yesterday->year;
            if ($periodMonth > 12) {
                $periodMonth = 1;
                $periodYear++;
            }
        }

        $period = PayrollPeriod::where('year', $periodYear)
            ->where('month', $periodMonth)
            ->first();

        if (! $period) {
            return;
        }

        $systemUser = User::first();

        if (! $systemUser) {
            return;
        }

        $employees = Employee::whereHas('user', fn ($q) => $q->where('is_active', true))
            ->where(fn ($q) => $q->whereNull('resign_date')->orWhere('resign_date', '>=', $yesterday->toDateString()))
            ->get();

        foreach ($employees as $employee) {
            $payroll = Payroll::where('payroll_period_id', $period->id)
                ->where('employee_id', $employee->id)
                ->where('status', PayrollStatus::UNPAID)
                ->first();

            if (! $payroll) {
                continue;
            }

            $attendance = Attendance::where('employee_id', $employee->id)
                ->where('attendance_date', $yesterday->toDateString())
                ->first();

            if ($attendance) {
                $this->handleLate($attendance, $payroll, $hrSettings, $systemUser, $yesterday);
            } else {
                $this->handleAbsent($payroll, $hrSettings, $systemUser, $yesterday, $employee);
            }
        }
    }

    private function handleLate(
        Attendance $attendance,
        Payroll $payroll,
        HRSettings $hrSettings,
        User $systemUser,
        CarbonInterface $date
    ): void {
        if ($hrSettings->late_penalty_amount <= 0) {
            return;
        }

        $checkInTime = $attendance->check_in_at;
        [$officeHour, $officeMinute] = explode(':', $hrSettings->scheduled_check_in_time);
        $officeStart = $date->copy()->setTime((int) $officeHour, (int) $officeMinute, 0);

        if (! $checkInTime || $checkInTime->lte($officeStart)) {
            return;
        }

        $lateMinutes = (int) $checkInTime->diffInMinutes($officeStart);

        $existing = PayrollAdjustment::where('payroll_id', $payroll->id)
            ->where('attendance_id', $attendance->id)
            ->where('type', PayrollAdjustmentType::DEDUCTION)
            ->exists();

        if ($existing) {
            return;
        }

        DB::transaction(function () use ($payroll, $attendance, $hrSettings, $systemUser, $date, $lateMinutes) {
            PayrollAdjustment::create([
                'payroll_id' => $payroll->id,
                'created_by_id' => $systemUser->id,
                'attendance_id' => $attendance->id,
                'type' => PayrollAdjustmentType::DEDUCTION,
                'amount' => $hrSettings->late_penalty_amount,
                'description' => 'Denda keterlambatan '.$date->format('d/m/Y').' - '.$lateMinutes.' menit',
            ]);

            $this->recalculatePayroll($payroll);
        });
    }

    private function handleAbsent(
        Payroll $payroll,
        HRSettings $hrSettings,
        User $systemUser,
        CarbonInterface $date,
        Employee $employee
    ): void {
        if ($hrSettings->absent_penalty_amount <= 0) {
            return;
        }

        $description = 'Denda ketidakhadiran '.$date->format('d/m/Y');

        $existing = PayrollAdjustment::where('payroll_id', $payroll->id)
            ->where('type', PayrollAdjustmentType::DEDUCTION)
            ->where('description', $description)
            ->exists();

        if ($existing) {
            return;
        }

        DB::transaction(function () use ($payroll, $hrSettings, $systemUser, $description) {
            PayrollAdjustment::create([
                'payroll_id' => $payroll->id,
                'created_by_id' => $systemUser->id,
                'attendance_id' => null,
                'type' => PayrollAdjustmentType::DEDUCTION,
                'amount' => $hrSettings->absent_penalty_amount,
                'description' => $description,
            ]);

            $this->recalculatePayroll($payroll);
        });
    }

    private function recalculatePayroll(Payroll $payroll): void
    {
        $payroll->refresh();

        $bonuses = $payroll->payrollAdjustments()
            ->where('type', PayrollAdjustmentType::BONUS)
            ->sum('amount');

        $deductions = $payroll->payrollAdjustments()
            ->where('type', PayrollAdjustmentType::DEDUCTION)
            ->sum('amount');

        $payroll->update([
            'bonus_amount' => $bonuses,
            'deduction_amount' => $deductions,
            'total_amount' => $payroll->base_salary + $bonuses - $deductions,
        ]);
    }
}
