<?php

namespace App\Http\Controllers;

use App\Http\Requests\GeneratePermanentEmployeeSalariesRequest;
use App\Http\Requests\PayPermanentEmployeeSalaryPaymentRequest;
use App\Http\Requests\StorePermanentEmployeeSalaryPaymentRequest;
use App\Http\Requests\UpdatePermanentEmployeeSalaryPaymentRequest;
use App\Models\PermanentEmployee;
use App\Models\PermanentEmployeeAdvance;
use App\Models\PermanentEmployeePunishment;
use App\Models\PermanentEmployeeSalaryPayment;
use App\Support\PermanentEmployeeMonthlyAttendance;
use App\Support\SalaryMonth;
use App\Support\SalaryPaymentStatus;
use App\Support\ShamsiMonthAttendance;
use Carbon\Carbon;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

class PermanentEmployeeSalaryPaymentController extends Controller
{
    private const PER_PAGE_OPTIONS = [5, 10, 25, 50];

    public function manage(Request $request): Response|RedirectResponse
    {
        $monthStart = $this->resolveSalaryMonthStart($request);
        $salaryMonth = SalaryMonth::urlKey($monthStart);
        $requestedMonth = $request->string('salary_month')->trim()->toString();

        if ($requestedMonth !== '' && $requestedMonth !== $salaryMonth) {
            return redirect()->route('employees.permanent.salaries.manage', [
                'salary_month' => $salaryMonth,
            ]);
        }

        $currentMonthStart = SalaryMonth::defaultStart();
        $period = SalaryMonth::period($monthStart);

        PermanentEmployeeSalaryPayment::query()
            ->forSalaryMonth($salaryMonth)
            ->where('status', SalaryPaymentStatus::DRAFT)
            ->where('deduct_absent_days', false)
            ->update(['deduct_absent_days' => true]);

        PermanentEmployeeSalaryPayment::refreshDraftsForSalaryMonth($salaryMonth);

        $monthSalaries = PermanentEmployeeSalaryPayment::query()
            ->with([
                'permanentEmployee.designation:id,name',
                'paidByUser:id,name',
            ])
            ->join(
                'permanent_employees',
                'permanent_employees.id',
                '=',
                'permanent_employee_salary_payments.permanent_employee_id',
            )
            ->forSalaryMonth($salaryMonth)
            ->orderBy('permanent_employees.full_name')
            ->select('permanent_employee_salary_payments.*')
            ->get()
            ->map(fn (PermanentEmployeeSalaryPayment $payment): array => $this->paymentListPayload($payment, includeEmployee: true));

        $existingEmployeeIds = $monthSalaries->pluck('permanent_employee_id')->all();

        $pendingActiveCount = PermanentEmployee::query()
            ->where('is_active', true)
            ->when($existingEmployeeIds !== [], fn ($query) => $query->whereNotIn('id', $existingEmployeeIds))
            ->count();

        return Inertia::render('employees/permanent/salaries/manage', [
            'salaryMonth' => $salaryMonth,
            'salaryMonthLabel' => SalaryMonth::displayLabel($monthStart),
            'salaryMonthPeriod' => [
                'from' => $period['start']->toDateString(),
                'to' => $period['end']->toDateString(),
                'counted_through' => ShamsiMonthAttendance::lastCountableDay($monthStart)->toDateString(),
                'shamsi_label' => SalaryMonth::displayLabel($monthStart),
                'jalali_year' => $period['jalali_year'],
                'jalali_month' => $period['jalali_month'],
            ],
            'attendanceRecordsUrl' => route('employees.attendance.index', [
                'salary_month' => $salaryMonth,
            ]),
            'currentSalaryMonth' => SalaryMonth::urlKey($currentMonthStart),
            'currentSalaryMonthLabel' => SalaryMonth::displayLabel($currentMonthStart),
            'monthSalaries' => $monthSalaries,
            'isGenerated' => $monthSalaries->isNotEmpty(),
            'canGenerate' => $pendingActiveCount > 0,
            'pendingActiveCount' => $pendingActiveCount,
            'activeStaffCount' => PermanentEmployee::query()->where('is_active', true)->count(),
        ]);
    }

    public function recalculateFromAttendance(Request $request): RedirectResponse
    {
        PermanentEmployeeSalaryPayment::query()
            ->where('status', SalaryPaymentStatus::DRAFT)
            ->where('deduct_absent_days', false)
            ->update(['deduct_absent_days' => true]);

        $monthStart = $this->resolveSalaryMonthStart($request);
        $salaryMonth = SalaryMonth::urlKey($monthStart);

        $refreshed = PermanentEmployeeSalaryPayment::refreshDraftsForSalaryMonth($salaryMonth);

        return redirect()
            ->route('employees.permanent.salaries.manage', ['salary_month' => $salaryMonth])
            ->with('status', __('Recalculated :count salary record(s) from attendance.', [
                'count' => $refreshed,
            ]));
    }

    public function generate(GeneratePermanentEmployeeSalariesRequest $request): RedirectResponse
    {
        $salaryMonth = $request->validated('salary_month');
        $period = SalaryMonth::period($salaryMonth);
        $monthStart = $period['start'];
        $monthEnd = $period['end'];
        $monthKey = SalaryMonth::urlKey($monthStart);
        $monthLabel = SalaryMonth::displayLabel($monthStart);

        $existingEmployeeIds = PermanentEmployeeSalaryPayment::query()
            ->forSalaryMonth($monthKey)
            ->pluck('permanent_employee_id')
            ->all();

        $deductAbsentDays = (bool) ($request->validated('deduct_absent_days') ?? true);

        $employees = PermanentEmployee::query()
            ->where('is_active', true)
            ->whereDate('joining_date', '<=', $monthEnd)
            ->when($existingEmployeeIds !== [], fn ($query) => $query->whereNotIn('id', $existingEmployeeIds))
            ->orderBy('full_name')
            ->get();

        $absentCounts = PermanentEmployeeMonthlyAttendance::absentDayCountsForEmployees(
            $employees->pluck('id')->all(),
            $monthStart,
        );

        $created = 0;

        foreach ($employees as $employee) {
            $proration = PermanentEmployeeMonthlyAttendance::proratedBaseSalary($employee, $monthStart);

            if ($proration['employed_days'] === 0) {
                continue;
            }

            $absentDays = $absentCounts[$employee->id] ?? 0;
            $absentMeta = [
                'absent_days' => $absentDays,
                'absent_deduction' => 0,
                'deduct_absent_days' => $deductAbsentDays,
            ];

            if ($deductAbsentDays && $absentDays > 0) {
                $absentMeta['absent_deduction'] = round(
                    PermanentEmployeeMonthlyAttendance::absentDeductionDailyRate($employee, $monthStart)
                        * $absentDays,
                    2,
                );
            }

            $payment = new PermanentEmployeeSalaryPayment([
                'permanent_employee_id' => $employee->id,
                'salary_month' => $monthKey,
                'base_salary' => $proration['base_salary'],
                'allowance' => 0,
                'salary_increment' => 0,
                'advance_deduction' => 0,
                'status' => SalaryPaymentStatus::DRAFT,
                ...$absentMeta,
            ]);
            $payment->recalculateAmount();
            $payment->save();
            $created++;
        }

        if ($created > 0) {
            PermanentEmployeeSalaryPayment::refreshDraftsForSalaryMonth($monthKey);
        }

        $message = $created > 0
            ? __(':count salary record(s) generated for :month.', [
                'count' => $created,
                'month' => $monthLabel,
            ])
            : __('All active staff already have salary records for :month.', ['month' => $monthLabel]);

        return redirect()
            ->route('employees.permanent.salaries.manage', ['salary_month' => $monthKey])
            ->with('status', $message);
    }

    public function update(
        UpdatePermanentEmployeeSalaryPaymentRequest $request,
        PermanentEmployeeSalaryPayment $salaryPayment,
    ): RedirectResponse {
        $validated = $request->validated();

        $salaryPayment->fill([
            'allowance' => $validated['allowance'],
            'salary_increment' => $validated['salary_increment'],
            'advance_deduction' => $validated['advance_deduction'],
        ]);
        $salaryPayment->refreshDraftFromContract();
        $salaryPayment->loadMissing('permanentEmployee');

        return redirect()
            ->route('employees.permanent.salaries.manage', [
                'salary_month' => $salaryPayment->salary_month?->format('Y-m-d'),
            ])
            ->with('status', __('Salary updated for :name.', [
                'name' => $salaryPayment->permanentEmployee->full_name,
            ]));
    }

    public function pay(
        PayPermanentEmployeeSalaryPaymentRequest $request,
        PermanentEmployeeSalaryPayment $salaryPayment,
    ): RedirectResponse {
        $validated = $request->validated();
        $paymentMethod = $validated['payment_method'];

        $attributes = [
            'payment_method' => $paymentMethod,
            'paid_at' => now(),
            'paid_by' => $request->user()?->id,
            'status' => SalaryPaymentStatus::PAID,
        ];

        if ($paymentMethod === PermanentEmployeeSalaryPayment::PAYMENT_METHOD_FINGERPRINT) {
            $attributes['fingerprint_template'] = $validated['fingerprint_data'];
            $attributes['fingerprint_captured_at'] = now();
        }

        $salaryPayment->refreshDraftFromContract();
        $salaryPayment->update($attributes);

        $message = $paymentMethod === PermanentEmployeeSalaryPayment::PAYMENT_METHOD_MANUAL
            ? __('Monthly salary paid manually.')
            : __('Monthly salary paid with fingerprint.');

        return redirect()
            ->route('employees.permanent.salaries.manage', [
                'salary_month' => $salaryPayment->salary_month?->format('Y-m-d'),
            ])
            ->with('status', $message);
    }

    public function index(Request $request, PermanentEmployee $permanentEmployee): Response
    {
        $perPage = $request->integer('per_page', 10);
        if (! in_array($perPage, self::PER_PAGE_OPTIONS, true)) {
            $perPage = 10;
        }

        $search = $request->string('search')->trim()->toString();

        $payments = $permanentEmployee->salaryPayments()
            ->with('paidByUser:id,name')
            ->when($search !== '', function ($query) use ($search): void {
                $query->where(function ($q) use ($search): void {
                    $q->where('amount', 'like', '%'.$search.'%')
                        ->orWhere('salary_month', 'like', '%'.$search.'%')
                        ->orWhere('payment_method', 'like', '%'.$search.'%');
                });
            })
            ->orderByDesc('salary_month')
            ->paginate($perPage)
            ->withQueryString()
            ->through(function (PermanentEmployeeSalaryPayment $payment): array {
                if ($payment->isDraft()) {
                    $payment->refreshDraftFromContract();
                }

                return $this->paymentListPayload($payment, includeEmployee: false);
            });

        return Inertia::render('employees/permanent/salaries/index', [
            'employee' => $this->employeeSummaryPayload($permanentEmployee),
            'payments' => $payments,
            'filters' => [
                'search' => $search,
                'per_page' => $perPage,
            ],
        ]);
    }

    public function store(
        StorePermanentEmployeeSalaryPaymentRequest $request,
        PermanentEmployee $permanentEmployee,
    ): RedirectResponse {
        $validated = $request->validated();
        $paymentMethod = $validated['payment_method'];

        $salaryPayment = PermanentEmployeeSalaryPayment::query()
            ->where('permanent_employee_id', $permanentEmployee->id)
            ->whereDate('salary_month', $validated['salary_month'])
            ->firstOrFail();

        $attributes = [
            'payment_method' => $paymentMethod,
            'paid_at' => now(),
            'paid_by' => $request->user()?->id,
            'status' => SalaryPaymentStatus::PAID,
        ];

        if ($paymentMethod === PermanentEmployeeSalaryPayment::PAYMENT_METHOD_FINGERPRINT) {
            $attributes['fingerprint_template'] = $validated['fingerprint_data'];
            $attributes['fingerprint_captured_at'] = now();
        }

        $salaryPayment->refreshDraftFromContract();
        $salaryPayment->update($attributes);

        $message = $paymentMethod === PermanentEmployeeSalaryPayment::PAYMENT_METHOD_MANUAL
            ? __('Monthly salary recorded manually.')
            : __('Monthly salary recorded with fingerprint.');

        return redirect()
            ->route('employees.permanent.salaries.index', $permanentEmployee)
            ->with('status', $message);
    }

    /**
     * @return array<string, mixed>
     */
    private function employeeSummaryPayload(PermanentEmployee $employee): array
    {
        $employee->loadMissing('designation:id,name');

        return [
            'id' => $employee->id,
            'full_name' => $employee->full_name,
            'monthly_salary' => (string) $employee->monthly_salary,
            'designation_name' => $employee->designation?->name,
            'image_url' => $employee->image_url,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function paymentListPayload(PermanentEmployeeSalaryPayment $payment, bool $includeEmployee = false): array
    {
        $payment->loadMissing('permanentEmployee.designation:id,name', 'paidByUser:id,name');

        $useContractFigures = $payment->isDraft() && $payment->salary_month !== null;

        if ($useContractFigures) {
            $figures = $payment->figuresFromContract();
            $proration = [
                'employed_days' => $figures['employed_days'],
                'days_in_month' => $figures['days_in_month'],
                'is_prorated' => $figures['is_prorated'],
            ];
            $baseSalary = $figures['base_salary'];
            $absentDays = $figures['absent_days'];
            $absentDeduction = $figures['absent_deduction'];
            $punishmentTotal = $figures['punishment_total'];
            $punishmentDates = $figures['punishment_dates'];
            $advanceDeduction = $figures['advance_deduction'];
            $outstandingAdvanceBalance = $figures['outstanding_advance_balance'];
            $advanceItems = $figures['advance_items'];
            $amount = $figures['amount'];
        } else {
            $proration = $payment->salary_month !== null
                ? PermanentEmployeeMonthlyAttendance::proratedBaseSalary(
                    $payment->permanentEmployee,
                    Carbon::parse($payment->salary_month),
                )
                : [
                    'days_in_month' => 0,
                    'employed_days' => 0,
                    'is_prorated' => false,
                ];
            $baseSalary = (float) $payment->base_salary;
            $absentDays = (int) $payment->absent_days;
            $absentDeduction = (float) $payment->absent_deduction;
            $amount = (float) $payment->amount;
            $advanceDeduction = (float) $payment->advance_deduction;
            $outstandingAdvanceBalance = PermanentEmployeeAdvance::outstandingBalance(
                $payment->permanent_employee_id,
                $payment->id,
            );
            $advanceItems = PermanentEmployeeAdvance::itemsForEmployee($payment->permanent_employee_id);

            if ($payment->salary_month !== null) {
                $punishmentSummary = PermanentEmployeePunishment::summaryForSalaryMonth(
                    $payment->permanent_employee_id,
                    Carbon::parse($payment->salary_month),
                );
                $punishmentTotal = $punishmentSummary['total'];
                $punishmentDates = $punishmentSummary['effective_dates'];
            } else {
                $punishmentTotal = 0.0;
                $punishmentDates = [];
            }
        }

        $payload = [
            'id' => $payment->id,
            'salary_month' => $payment->salary_month?->format('Y-m-d'),
            'base_salary' => (string) $baseSalary,
            'contract_salary' => (string) $payment->permanentEmployee->monthly_salary,
            'employed_days' => $proration['employed_days'],
            'days_in_month' => $proration['days_in_month'],
            'is_prorated' => $proration['is_prorated'],
            'allowance' => (string) $payment->allowance,
            'salary_increment' => (string) $payment->salary_increment,
            'absent_days' => $absentDays,
            'absent_deduction' => (string) $absentDeduction,
            'punishment_total' => (string) $punishmentTotal,
            'punishment_dates' => $punishmentDates,
            'advance_deduction' => (string) $advanceDeduction,
            'advance_total' => (string) $advanceDeduction,
            'outstanding_advance_balance' => (string) $outstandingAdvanceBalance,
            'advance_items' => $advanceItems,
            'deduct_absent_days' => $payment->deduct_absent_days,
            'amount' => (string) $amount,
            'status' => $payment->status,
            'is_draft' => $payment->isDraft(),
            'is_paid' => $payment->isPaid(),
            'payment_method' => $payment->payment_method,
            'paid_at' => $payment->paid_at?->format('Y-m-d H:i'),
            'paid_by_name' => $payment->paidByUser?->name,
            'was_paid_with_fingerprint' => $payment->wasPaidWithFingerprint(),
            'was_paid_manually' => $payment->wasPaidManually(),
        ];

        if ($incrementDisplay = $payment->incrementSalaryDisplay()) {
            $payload = array_merge($payload, $incrementDisplay);
        }

        if ($includeEmployee) {
            $employee = $payment->permanentEmployee;

            $payload['permanent_employee_id'] = $employee->id;
            $payload['staff_name'] = $employee->full_name;
            $payload['designation_name'] = $employee->designation?->name;
        }

        return $payload;
    }

    private function resolveSalaryMonthStart(Request $request): Carbon
    {
        return SalaryMonth::normalizeStart($request->string('salary_month')->trim()->toString());
    }
}
