<?php

namespace App\Http\Controllers;

use App\Models\Payslip;
use App\Models\Staff;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class PayrollController extends Controller
{
    /** Finance/admin: browse generated payslips for a period. */
    public function index(Request $request)
    {
        $this->authorizeFinance();

        $month = (int) $request->get('month', now()->month);
        $year  = (int) $request->get('year', now()->year);

        $payslips = Payslip::with('staff')
            ->where('period_month', $month)->where('period_year', $year)
            ->join('staff', 'staff.id', '=', 'payslips.staff_id')
            ->orderBy('staff.name')
            ->select('payslips.*')
            ->paginate(30)->withQueryString();

        return view('payroll.index', compact('payslips', 'month', 'year'));
    }

    public function create(Request $request)
    {
        $this->authorizeFinance();

        $staff = null;
        if ($request->filled('staff_id')) {
            $staff = Staff::with(['site', 'bank'])->findOrFail($request->get('staff_id'));
        }

        return view('payroll.create', [
            'staffList' => Staff::where('status', 'active')->orderBy('name')->get(['id', 'name', 'passport_no']),
            'staff'     => $staff,
            'month'     => (int) $request->get('month', now()->month),
            'year'      => (int) $request->get('year', now()->year),
        ]);
    }

    public function store(Request $request)
    {
        $this->authorizeFinance();
        $data = $this->validatePayslip($request);
        $staff = Staff::with(['site', 'bank'])->findOrFail($data['staff_id']);

        $exists = Payslip::where('staff_id', $staff->id)
            ->where('period_month', $data['period_month'])
            ->where('period_year', $data['period_year'])->exists();
        if ($exists) {
            return back()->withInput()->with('error', 'A payslip for this staff and period already exists.');
        }

        $payslip = DB::transaction(function () use ($data, $staff, $request) {
            $payslip = Payslip::create($this->buildAttributes($data, $staff));
            $this->syncLines($payslip, $request);
            $payslip->recalculate();
            $payslip->save();
            return $payslip;
        });

        return redirect()->route('payroll.show', $payslip)->with('success', 'Payslip created.');
    }

    public function show(Payslip $payslip)
    {
        $this->authorizeFinance();
        $payslip->load(['staff', 'lines']);
        return view('payroll.show', compact('payslip'));
    }

    public function edit(Payslip $payslip)
    {
        $this->authorizeFinance();
        abort_if($payslip->status === 'finalized', 403, 'Finalized payslips cannot be edited.');
        $payslip->load('lines');
        return view('payroll.edit', [
            'payslip' => $payslip,
            'staff'   => $payslip->staff,
        ]);
    }

    public function update(Request $request, Payslip $payslip)
    {
        $this->authorizeFinance();
        abort_if($payslip->status === 'finalized', 403);
        $data = $this->validatePayslip($request, $payslip);
        $staff = $payslip->staff;

        DB::transaction(function () use ($payslip, $data, $staff, $request) {
            $payslip->update($this->buildAttributes($data, $staff, false));
            $payslip->lines()->delete();
            $this->syncLines($payslip, $request);
            $payslip->recalculate();
            $payslip->save();
        });

        return redirect()->route('payroll.show', $payslip)->with('success', 'Payslip updated.');
    }

    public function finalize(Payslip $payslip)
    {
        $this->authorizeFinance();
        $payslip->update(['status' => 'finalized', 'generated_by' => auth()->id()]);
        return redirect()->route('payroll.show', $payslip)->with('success', 'Payslip finalized.');
    }

    public function destroy(Payslip $payslip)
    {
        $this->authorizeFinance();
        abort_if($payslip->status === 'finalized' && ! auth()->user()->isAdmin(), 403);
        $payslip->delete();
        return redirect()->route('payroll.index')->with('success', 'Payslip deleted.');
    }

    /** Single payslip PDF (finance or the owning staff). */
    public function pdf(Payslip $payslip)
    {
        $this->authorizeViewPayslip($payslip);
        $payslip->load(['staff', 'lines']);
        $pdf = Pdf::loadView('payslips.pdf', ['payslips' => collect([$payslip])])->setPaper('a4');
        return $pdf->download('payslip-'.$payslip->staff->name.'-'.$payslip->periodLabel().'.pdf');
    }

    /** Whole-month batch, two payslips per page. */
    public function batchPdf(Request $request)
    {
        $this->authorizeFinance();
        $month = (int) $request->get('month', now()->month);
        $year  = (int) $request->get('year', now()->year);

        $payslips = Payslip::with(['staff', 'lines'])
            ->where('period_month', $month)->where('period_year', $year)
            ->get()->sortBy(fn ($p) => $p->staff->name)->values();

        abort_if($payslips->isEmpty(), 404, 'No payslips for this period.');

        $pdf = Pdf::loadView('payslips.pdf', compact('payslips'))->setPaper('a4');
        return $pdf->download("payslips-{$year}-{$month}.pdf");
    }

    /** Staff self-service list. */
    public function mine()
    {
        $staffId = auth()->user()->staff_id;
        abort_unless($staffId, 404, 'Your login is not linked to a staff record.');
        $payslips = Payslip::where('staff_id', $staffId)->where('status', 'finalized')
            ->orderByDesc('period_year')->orderByDesc('period_month')->paginate(24);
        return view('payslips.mine', compact('payslips'));
    }

    // ---- helpers ----

    private function validatePayslip(Request $request, ?Payslip $payslip = null): array
    {
        return $request->validate([
            'staff_id'        => ['required', 'exists:staff,id'],
            'period_month'    => ['required', 'integer', 'between:1,12'],
            'period_year'     => ['required', 'integer', 'between:2000,2100'],
            'basic_salary'    => ['nullable', 'numeric'],
            'working_days'    => ['nullable', 'numeric'],
            'days_divisor'    => ['nullable', 'integer'],
            'working_units'   => ['nullable', 'numeric'],
            'al_amount'       => ['nullable', 'numeric'],
            'ul_amount'       => ['nullable', 'numeric'],
            'ot_hours'        => ['nullable', 'numeric'],
            'ot_days'         => ['nullable', 'numeric'],
            'ot_amount'       => ['nullable', 'numeric'],
            'ph_hours'        => ['nullable', 'numeric'],
            'ph_days'         => ['nullable', 'numeric'],
            'ph_amount'       => ['nullable', 'numeric'],
            'epf_employee'    => ['nullable', 'numeric'],
            'socso_employee'  => ['nullable', 'numeric'],
            'eis_employee'    => ['nullable', 'numeric'],
            'income_tax_pcb'  => ['nullable', 'numeric'],
            'income_tax_cp38' => ['nullable', 'numeric'],
            'unpaid_leave'    => ['nullable', 'numeric'],
            'advance'         => ['nullable', 'numeric'],
            'loan'            => ['nullable', 'numeric'],
            'ded_label'       => ['nullable', 'array'],
            'ded_amount'      => ['nullable', 'array'],
            'alw_label'       => ['nullable', 'array'],
            'alw_amount'      => ['nullable', 'array'],
        ]);
    }

    private function buildAttributes(array $data, Staff $staff, bool $withSnapshot = true): array
    {
        $numeric = collect($data)->only([
            'basic_salary', 'working_days', 'days_divisor', 'working_units', 'al_amount', 'ul_amount',
            'ot_hours', 'ot_days', 'ot_amount', 'ph_hours', 'ph_days', 'ph_amount',
            'epf_employee', 'socso_employee', 'eis_employee', 'income_tax_pcb', 'income_tax_cp38',
            'unpaid_leave', 'advance', 'loan',
        ])->map(fn ($v) => $v === null ? 0 : $v)->toArray();

        $attrs = array_merge($numeric, [
            'staff_id'     => $staff->id,
            'period_month' => $data['period_month'],
            'period_year'  => $data['period_year'],
        ]);

        if ($withSnapshot) {
            $attrs = array_merge($attrs, [
                'snap_name'        => $staff->name,
                'snap_passport'    => $staff->passport_no,
                'snap_designation' => $staff->designation,
                'snap_department'  => $staff->department,
                'snap_branch'      => $staff->site?->name,
                'snap_bank_ac'     => $staff->account_number,
                'snap_date_joined' => $staff->date_of_join,
            ]);
        }

        return $attrs;
    }

    private function syncLines(Payslip $payslip, Request $request): void
    {
        foreach (['ded' => 'deduction', 'alw' => 'allowance'] as $prefix => $category) {
            $labels  = $request->input("{$prefix}_label", []);
            $amounts = $request->input("{$prefix}_amount", []);
            foreach ($labels as $i => $label) {
                $label = trim((string) $label);
                if ($label === '') {
                    continue;
                }
                $payslip->lines()->create([
                    'category' => $category,
                    'label'    => $label,
                    'amount'   => (float) ($amounts[$i] ?? 0),
                ]);
            }
        }
    }

    private function authorizeFinance(): void
    {
        abort_unless(auth()->user()->isAdmin() || auth()->user()->isFinance(), 403);
    }

    private function authorizeViewPayslip(Payslip $payslip): void
    {
        $u = auth()->user();
        if ($u->isAdmin() || $u->isFinance()) {
            return;
        }
        if ($u->isStaff() && $u->staff_id === $payslip->staff_id && $payslip->status === 'finalized') {
            return;
        }
        abort(403);
    }
}
