<?php

namespace App\Services;

use App\Models\Employee;
use App\Models\EmployeeAdvance;
use App\Models\EmployeeAdvanceRepayment;
use App\Models\EmployeeAttendance;
use App\Models\Payroll;
use App\Models\PayrollItem;
use App\Models\PayrollPayment;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class PayrollService
{
    public static function generate(
        int $userId,
        int $month,
        int $year,
        ?string $payrollDate = null,
        array $advanceDeductions = []
    ): Payroll {
        return DB::transaction(function () use ($userId, $month, $year, $payrollDate, $advanceDeductions) {
            $payroll = Payroll::firstOrCreate(
                [
                    'user_id'       => $userId,
                    'payroll_month' => $month,
                    'payroll_year'  => $year,
                ],
                [
                    'payroll_date' => $payrollDate
                        ? Carbon::parse($payrollDate)->toDateString()
                        : Carbon::create($year, $month, 1)->endOfMonth()->toDateString(),
                    'status' => 'draft',
                ]
            );

            if ($payroll->status !== 'draft') {
                throw new \RuntimeException('Only draft payroll can be regenerated.');
            }

            PayrollItem::where('payroll_id', $payroll->id)->delete();

            $startDate = Carbon::create($year, $month, 1)->startOfMonth()->toDateString();
            $endDate   = Carbon::create($year, $month, 1)->endOfMonth()->toDateString();

            $employees = Employee::where('user_id', $userId)
                ->where('status', 'active')
                ->with('currentSalaryStructure')
                ->orderBy('name')
                ->get();

            foreach ($employees as $employee) {
                $structure = $employee->currentSalaryStructure;

                if (!$structure) {
                    Log::warning('Payroll skipped: missing salary structure', [
                        'user_id'       => $userId,
                        'employee_id'   => $employee->id,
                        'employee_name' => $employee->name,
                    ]);
                    continue;
                }

                $attendanceQuery = EmployeeAttendance::where('user_id', $userId)
                    ->where('employee_id', $employee->id)
                    ->whereBetween('attendance_date', [$startDate, $endDate]);

                $totalPresentDays = (float) (clone $attendanceQuery)->where('day_status', 'present')->count();
                $totalAbsentDays  = (float) (clone $attendanceQuery)->where('day_status', 'absent')->count();
                $totalLeaveDays   = (float) (clone $attendanceQuery)->where('day_status', 'leave')->count();
                $totalHolidayDays = (float) (clone $attendanceQuery)->where('day_status', 'holiday')->count();

                $totalOvertimeHoursRaw = (float) (clone $attendanceQuery)->sum('overtime_hours');
                $totalRoz              = (float) (clone $attendanceQuery)->sum('roz_value');

                $rateMonthlySalary  = money($structure->monthly_salary ?? 0);
                $ratePerRoz         = money($structure->per_roz_rate ?? 0);
                $rateBasicWorkHours = (float) ($structure->basic_work_hours ?? 0);
                $rateOvertimeRate   = money($structure->overtime_rate ?? 0);

                $perDaySalary       = 0;
                $basicAmount        = 0;
                $absentDeduction    = 0;
                $overtimeAmount     = 0;
                $totalOvertimeHours = 0.00;

                if ($employee->employee_type === 'monthly') {
                    $totalOvertimeHours = $totalOvertimeHoursRaw;

                    $salaryBasisDays = $totalPresentDays + $totalAbsentDays + $totalLeaveDays;

                    if ($salaryBasisDays > 0) {
                        $perDaySalary = money($rateMonthlySalary / $salaryBasisDays);
                        $absentDeduction = money($perDaySalary * $totalAbsentDays);
                        $basicAmount = money(max(0, $rateMonthlySalary - $absentDeduction));
                    } else {
                        $perDaySalary = 0;
                        $absentDeduction = 0;
                        $basicAmount = 0;
                    }

                    $overtimeAmount = money($rateOvertimeRate * $totalOvertimeHours);
                } else {
                    $totalOvertimeHours = 0;
                    $basicAmount        = money($ratePerRoz * $totalRoz);
                    $overtimeAmount     = 0;
                }

                $grossSalary     = money($basicAmount + $overtimeAmount);
                $deductionAmount = money($absentDeduction);
                $preNetSalary    = money(max(0, $grossSalary));

                $outstandingAdvance = money(self::getOutstandingAdvanceAmount(
                    userId: $userId,
                    employeeId: (int) $employee->id,
                    endDate: $endDate
                ));

                $requestedAdvanceDeduction = money($advanceDeductions[$employee->id] ?? 0);

                $advanceAdjusted = money(min(
                    $preNetSalary,
                    $outstandingAdvance,
                    max(0, $requestedAdvanceDeduction)
                ));

                $netSalary = money(max(0, $preNetSalary - $advanceAdjusted));

                $paidAmount = 0;
                $dueAmount  = money(max(0, $netSalary - $paidAmount));

                Log::info('Payroll Calculation Debug', [
                    'user_id'                     => $userId,
                    'employee_id'                 => $employee->id,
                    'employee_name'               => $employee->name,
                    'employee_type'               => $employee->employee_type,
                    'month'                       => $month,
                    'year'                        => $year,
                    'total_present_days'          => $totalPresentDays,
                    'total_absent_days'           => $totalAbsentDays,
                    'total_leave_days'            => $totalLeaveDays,
                    'total_holiday_days'          => $totalHolidayDays,
                    'total_overtime_hours'        => $totalOvertimeHours,
                    'total_roz'                   => $totalRoz,
                    'rate_monthly_salary'         => $rateMonthlySalary,
                    'rate_per_roz'                => $ratePerRoz,
                    'rate_basic_work_hours'       => $rateBasicWorkHours,
                    'rate_overtime_rate'          => $rateOvertimeRate,
                    'per_day_salary'              => $perDaySalary,
                    'absent_deduction'            => $absentDeduction,
                    'basic_amount'                => $basicAmount,
                    'overtime_amount'             => $overtimeAmount,
                    'gross_salary'                => $grossSalary,
                    'deduction_amount'            => $deductionAmount,
                    'pre_net_salary'              => $preNetSalary,
                    'outstanding_advance'         => $outstandingAdvance,
                    'requested_advance_deduction' => $requestedAdvanceDeduction,
                    'advance_adjusted'            => $advanceAdjusted,
                    'net_salary'                  => $netSalary,
                ]);

                PayrollItem::create([
                    'user_id'               => $userId,
                    'payroll_id'            => $payroll->id,
                    'employee_id'           => $employee->id,
                    'employee_type'         => $employee->employee_type,

                    'rate_monthly_salary'   => $rateMonthlySalary,
                    'rate_per_roz'          => $ratePerRoz,
                    'rate_basic_work_hours' => $rateBasicWorkHours,
                    'rate_overtime_rate'    => $rateOvertimeRate,

                    'total_present_days'    => $totalPresentDays,
                    'total_absent_days'     => $totalAbsentDays,
                    'total_leave_days'      => $totalLeaveDays,
                    'total_holiday_days'    => $totalHolidayDays,

                    'total_overtime_hours'  => $totalOvertimeHours,
                    'total_roz'             => $totalRoz,

                    'gross_salary'          => $grossSalary,
                    'overtime_amount'       => $overtimeAmount,
                    'deduction_amount'      => $deductionAmount,
                    'advance_adjusted'      => $advanceAdjusted,
                    'net_salary'            => $netSalary,

                    'paid_amount'           => $paidAmount,
                    'due_amount'            => $dueAmount,

                    'generated_at'          => now(),
                    'is_locked'             => false,
                    'remarks'               => null,
                ]);
            }

            return $payroll->fresh(['items.employee', 'payments']);
        });
    }

    public static function finalize(Payroll $payroll): void
    {
        DB::transaction(function () use ($payroll) {
            if ($payroll->status !== 'draft') {
                throw new \RuntimeException('Only draft payroll can be finalized.');
            }

            $userId = (int) ($payroll->user_id ?? 0);

            if ($userId <= 0) {
                throw new \RuntimeException('Invalid payroll user id.');
            }

            $items = PayrollItem::where('payroll_id', $payroll->id)
                ->with('employee')
                ->get();

            if ($items->isEmpty()) {
                throw new \RuntimeException('No payroll items found to finalize.');
            }

            $entryDate = $payroll->payroll_date
                ? Carbon::parse($payroll->payroll_date)->toDateTimeString()
                : now()->toDateTimeString();

            $endDate = Carbon::create(
                (int) $payroll->payroll_year,
                (int) $payroll->payroll_month,
                1
            )->endOfMonth()->toDateString();

            foreach ($items as $item) {
                $employeeId  = (int) ($item->employee_id ?? 0);
                $referenceId = (int) ($item->id ?? 0);

                if ($employeeId <= 0 || $referenceId <= 0) {
                    throw new \RuntimeException('Invalid payroll item detected during finalize.');
                }

                EmployeeLedgerService::deleteByReference(
                    $userId,
                    'payroll_item',
                    $referenceId
                );

                EmployeeLedgerService::post(
                    userId: $userId,
                    employeeId: $employeeId,
                    type: 'salary_generated',
                    referenceType: 'payroll_item',
                    referenceId: $referenceId,
                    debit: 0,
                    credit: money($item->net_salary ?? 0),
                    description: 'Salary generated for ' . sprintf(
                        '%02d-%04d',
                        (int) $payroll->payroll_month,
                        (int) $payroll->payroll_year
                    ),
                    referenceNo: 'PAY-' . (int) $payroll->id . '-' . $employeeId,
                    entryDate: $entryDate
                );

                self::applyAdvanceAdjustments(
                    userId: $userId,
                    payrollId: (int) $payroll->id,
                    employeeId: $employeeId,
                    employeeName: (string) ($item->employee->name ?? 'Employee'),
                    amount: money($item->advance_adjusted ?? 0),
                    endDate: $endDate,
                    repaymentDate: $payroll->payroll_date
                        ? Carbon::parse($payroll->payroll_date)->toDateString()
                        : now()->toDateString()
                );
            }

            $payroll->update([
                'status' => 'finalized',
            ]);

            PayrollItem::where('payroll_id', $payroll->id)->update([
                'is_locked' => true,
            ]);
        });
    }

    public static function delete(Payroll $payroll): void
    {
        DB::transaction(function () use ($payroll) {
            $hasPayments = PayrollPayment::where('payroll_id', $payroll->id)->exists();

            if ($hasPayments) {
                throw new \RuntimeException('This payroll has salary payments. Delete payments first.');
            }

            $userId = (int) ($payroll->user_id ?? 0);

            if ($userId <= 0) {
                throw new \RuntimeException('Invalid payroll user id.');
            }

            $items = PayrollItem::where('payroll_id', $payroll->id)->get();

            if (in_array($payroll->status, ['finalized', 'paid_partial', 'paid_full'])) {
                $endDate = Carbon::create(
                    (int) $payroll->payroll_year,
                    (int) $payroll->payroll_month,
                    1
                )->endOfMonth()->toDateString();

                foreach ($items as $item) {
                    $employeeId  = (int) ($item->employee_id ?? 0);
                    $referenceId = (int) ($item->id ?? 0);

                    if ($employeeId <= 0 || $referenceId <= 0) {
                        continue;
                    }

                    EmployeeLedgerService::deleteByReference(
                        $userId,
                        'payroll_item',
                        $referenceId
                    );

                    self::revertAdvanceAdjustments(
                        userId: $userId,
                        payrollId: (int) $payroll->id,
                        employeeId: $employeeId,
                        amount: money($item->advance_adjusted ?? 0),
                        endDate: $endDate
                    );
                }
            }

            PayrollItem::where('payroll_id', $payroll->id)->delete();
            $payroll->delete();
        });
    }

    protected static function getOutstandingAdvanceAmount(int $userId, int $employeeId, string $endDate): float
    {
        return (float) EmployeeAdvance::where('user_id', $userId)
            ->where('employee_id', $employeeId)
            ->whereDate('advance_date', '<=', $endDate)
            ->whereIn('status', ['active', 'partial_adjusted'])
            ->where('remaining_amount', '>', 0)
            ->sum('remaining_amount');
    }

    protected static function applyAdvanceAdjustments(
        int $userId,
        int $payrollId,
        int $employeeId,
        string $employeeName,
        float $amount,
        string $endDate,
        string $repaymentDate
    ): void {
        $amount = money($amount);

        if ($amount <= 0) {
            return;
        }

        $remainingToAdjust = $amount;

        $advances = EmployeeAdvance::where('user_id', $userId)
            ->where('employee_id', $employeeId)
            ->whereDate('advance_date', '<=', $endDate)
            ->whereIn('status', ['active', 'partial_adjusted'])
            ->where('remaining_amount', '>', 0)
            ->orderBy('advance_date')
            ->orderBy('id')
            ->get();

        foreach ($advances as $advance) {
            if ($remainingToAdjust <= 0) {
                break;
            }

            $currentRemaining = money($advance->remaining_amount);
            $currentAdjusted  = money($advance->adjusted_amount);

            if ($currentRemaining <= 0) {
                continue;
            }

            $use = min($remainingToAdjust, $currentRemaining);

            $newAdjusted  = money($currentAdjusted + $use);
            $newRemaining = money($currentRemaining - $use);

            $advance->update([
                'adjusted_amount'  => $newAdjusted,
                'remaining_amount' => max(0, $newRemaining),
                'status'           => $newRemaining <= 0 ? 'adjusted' : 'partial_adjusted',
                'payroll_id'       => $payrollId,
            ]);

            EmployeeAdvanceRepayment::create([
                'user_id'             => $userId,
                'employee_advance_id' => $advance->id,
                'employee_id'         => $employeeId,
                'user_account_id'     => $advance->user_account_id,
                'repayment_date'      => $repaymentDate,
                'amount'              => money($use),
                'source'              => 'payroll_adjustment',
                'payroll_id'          => $payrollId,
                'reference_no'        => 'PAYROLL-ADV-' . $payrollId . '-' . $advance->id,
                'note'                => 'Payroll finalized advance adjustment for ' . $employeeName,
            ]);

            $remainingToAdjust = money($remainingToAdjust - $use);
        }
    }

    protected static function revertAdvanceAdjustments(
        int $userId,
        int $payrollId,
        int $employeeId,
        float $amount,
        string $endDate
    ): void {
        $amount = money($amount);

        if ($amount <= 0) {
            return;
        }

        EmployeeAdvanceRepayment::where('user_id', $userId)
            ->where('employee_id', $employeeId)
            ->where('payroll_id', $payrollId)
            ->where('source', 'payroll_adjustment')
            ->delete();

        $remainingToRevert = $amount;

        $advances = EmployeeAdvance::where('user_id', $userId)
            ->where('employee_id', $employeeId)
            ->whereDate('advance_date', '<=', $endDate)
            ->where('payroll_id', $payrollId)
            ->where('adjusted_amount', '>', 0)
            ->orderByDesc('advance_date')
            ->orderByDesc('id')
            ->get();

        foreach ($advances as $advance) {
            if ($remainingToRevert <= 0) {
                break;
            }

            $currentAdjusted  = money($advance->adjusted_amount);
            $currentRemaining = money($advance->remaining_amount);

            if ($currentAdjusted <= 0) {
                continue;
            }

            $revert = min($remainingToRevert, $currentAdjusted);

            $newAdjusted  = money($currentAdjusted - $revert);
            $newRemaining = money($currentRemaining + $revert);

            $advance->update([
                'adjusted_amount'  => max(0, $newAdjusted),
                'remaining_amount' => $newRemaining,
                'status'           => $newAdjusted <= 0 ? 'active' : 'partial_adjusted',
                'payroll_id'       => $newAdjusted <= 0 ? null : $payrollId,
            ]);

            $remainingToRevert = money($remainingToRevert - $revert);
        }

        if ($remainingToRevert > 0) {
            Log::warning('Advance rollback incomplete while deleting payroll', [
                'user_id'              => $userId,
                'payroll_id'           => $payrollId,
                'employee_id'          => $employeeId,
                'requested_revert'     => $amount,
                'remaining_unreverted' => $remainingToRevert,
            ]);
        }
    }

    public static function refreshPaymentStatus(Payroll $payroll): void
    {
        $totalDue = money(PayrollItem::where('payroll_id', $payroll->id)->sum('due_amount'));

        if ($totalDue <= 0) {
            $payroll->update(['status' => 'paid_full']);
        } else {
            $payroll->update(['status' => 'paid_partial']);
        }
    }
}