<?php

namespace App\Modules\Payroll\Services;

use App\Modules\HR\Employees\Models\Employee;
use App\Modules\Payroll\Events\PayrollSnapshotsGenerated;
use App\Modules\Payroll\Repositories\PayrollAdjustmentRepository;
use App\Modules\Payroll\Repositories\PayrollEmployeeSnapshotRepository;
use App\Modules\Payroll\Repositories\PayrollPeriodRepository;
use App\Modules\Payroll\Support\PayrollPeriodStatus;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class PayrollSnapshotGenerationService
{
    public function __construct(
        private readonly PayrollPeriodRepository $periods,
        private readonly PayrollEmployeeSnapshotRepository $snapshots,
        private readonly PayrollInputAggregationService $aggregation,
        private readonly PayrollOvertimeAdjustmentSyncService $overtimeSync,
        private readonly PayrollAdjustmentRepository $adjustments,
        private readonly PayrollProtectionService $payrollProtection,
    ) {}

    public function generateSnapshots(int $periodId): void
    {
        $lock = Cache::lock('payroll-generate:'.$periodId, 600);
        if (! $lock->get()) {
            return;
        }
        try {
            $period = $this->periods->findById($periodId);
            if ($period === null) {
                return;
            }
            $this->payrollProtection->assertSnapshotRegenerationAllowed($period);
            if ($period->status !== PayrollPeriodStatus::DRAFT) {
                return;
            }
            $tid = (int) $period->tenant_id;
            $actorId = $period->created_by !== null ? (int) $period->created_by : null;
            $this->snapshots->deleteForPeriod($tid, $periodId);
            $from = $period->start_date->toDateString();
            $to = $period->end_date->toDateString();
            Employee::query()
                ->where('tenant_id', $tid)
                ->where('is_active', true)
                ->orderBy('id')
                ->chunkById(150, function ($employees) use ($tid, $from, $to, $periodId, $actorId): void {
                    foreach ($employees as $employee) {
                        $agg = $this->aggregation->aggregateForEmployee(
                            $tid,
                            (int) $employee->id,
                            $from,
                            $to,
                            $periodId,
                        );
                        $this->overtimeSync->syncForEmployee(
                            $tid,
                            (int) $employee->id,
                            $periodId,
                            (float) $agg['base_salary'],
                            (int) $agg['overtime_minutes'],
                            $actorId,
                        );
                        $adj = $this->adjustments->sumSignedForEmployeePeriod(
                            $tid,
                            (int) $employee->id,
                            $periodId,
                        );
                        $leaveDeduction = (float) data_get($agg['snapshot_data'], 'leave_deduction_amount', 0);
                        $gross = (string) round((float) $agg['base_salary'] + (float) $adj - $leaveDeduction, 2);
                        $this->snapshots->create([
                            'tenant_id' => $tid,
                            'payroll_period_id' => $periodId,
                            'employee_id' => (int) $employee->id,
                            'base_salary' => $agg['base_salary'],
                            'attendance_days' => $agg['attendance_days'],
                            'absent_days' => $agg['absent_days'],
                            'late_days' => $agg['late_days'],
                            'leave_days' => $agg['leave_days'],
                            'overtime_minutes' => $agg['overtime_minutes'],
                            'adjustments_total' => $adj,
                            'gross_amount' => $gross,
                            'snapshot_data' => array_merge($agg['snapshot_data'], [
                                'overtime_pay_included' => true,
                            ]),
                            'generated_at' => now(),
                        ]);
                    }
                });
            $this->periods->update($period, [
                'status' => PayrollPeriodStatus::PROCESSING,
                'processed_at' => now(),
            ]);
            $fresh = $this->periods->findById($periodId);
            if ($fresh !== null) {
                DB::afterCommit(function () use ($fresh): void {
                    event(new PayrollSnapshotsGenerated($fresh));
                });
            }
        } finally {
            $lock->release();
        }
    }
}
