<?php

namespace App\Controllers;

use App\Models\PayrollRunModel;
use App\Models\PayslipLineItemModel;
use App\Models\PayslipModel;
use App\Models\UserModel;
use Config\Lookups;

/**
 * Payroll & salary — manager-only (see Lookups::$permissionModules
 * ['payroll'] and the 'role:manager' route filter, not enforced here).
 * Record-keeping only: a payroll run generates real payslips with
 * manager-entered allowance/deduction lines and a computed net pay, but
 * no money is disbursed by this app — the manager pays staff through
 * their own existing means and marks the run "Paid" with a note. See
 * the build plan's payroll section for why both design choices
 * (manager-entered deductions, no disbursement integration) were made.
 */
class PayrollController extends BaseController
{
    public function index()
    {
        $runs = (new PayrollRunModel())->latest();
        $payslips = new PayslipModel();

        // Per-run summary (staff count + total net pay) for the list view —
        // small enough result sets that one query per run is fine, matching
        // how ExpenseController/BillingFinanceController compute lightweight
        // per-row aggregates rather than a single heavier join.
        $summaries = [];
        foreach ($runs as $run) {
            $slips = $payslips->forRun($run->id);
            $total = '0.00';
            foreach ($slips as $slip) {
                $total = money_add($total, $slip->net_pay);
            }
            $summaries[$run->id] = ['count' => count($slips), 'total' => $total];
        }

        return view('payroll/index', [
            'pageTitle' => 'Payroll',
            'runs'      => $runs,
            'summaries' => $summaries,
            'lookups'   => new Lookups(),
        ]);
    }

    public function new()
    {
        return view('payroll/new', [
            'pageTitle' => 'New Payroll Run',
        ]);
    }

    /**
     * Creates the run and one payslip per currently-active staff member,
     * pre-filled from users.basic_salary (0 if unset) — a snapshot at
     * generation time, per CreatePayslipsTable's migration comment.
     */
    public function create()
    {
        $periodMonth = $this->request->getPost('period_month');

        if (empty($periodMonth)) {
            return redirect()->back()->withInput()->with('error', 'Choose a pay period month.');
        }

        // Normalize "2026-08" (month input) to the first of the month.
        $periodMonth = date('Y-m-01', strtotime($periodMonth . '-01'));

        $runs = new PayrollRunModel();
        $data = [
            'period_month' => $periodMonth,
            'status'       => 'draft',
            'created_by'   => $this->currentUserId(),
        ];

        if (! $runs->insert($data)) {
            return $this->redirectWithModelErrors($runs);
        }

        $runId = $runs->getInsertID();

        $staff = (new UserModel())->where('is_active', 1)->findAll();
        $payslips = new PayslipModel();

        foreach ($staff as $member) {
            $basicSalary = $member->basic_salary ?? '0.00';

            $payslips->insert([
                'payroll_run_id'     => $runId,
                'user_id'            => $member->id,
                'full_name_snapshot' => $member->full_name,
                'basic_salary'       => $basicSalary,
                'total_allowances'   => '0.00',
                'total_deductions'   => '0.00',
                'net_pay'            => $basicSalary,
            ]);
        }

        $this->logActivity('payroll', 'Created a payroll run for ' . date('F Y', strtotime($periodMonth)) . ' (' . count($staff) . ' staff).');

        return redirect()->to('payroll/' . $runId)->with('success', 'Payroll run created.');
    }

    public function show(int $id)
    {
        $run = (new PayrollRunModel())->find($id);

        if ($run === null) {
            return redirect()->to('payroll')->with('error', 'Payroll run not found.');
        }

        $payslips = (new PayslipModel())->forRun($id);
        $lineItemModel = new PayslipLineItemModel();

        $lineItems = [];
        foreach ($payslips as $payslip) {
            $lineItems[$payslip->id] = $lineItemModel->forPayslip($payslip->id);
        }

        return view('payroll/show', [
            'pageTitle' => 'Payroll Run — ' . date('F Y', strtotime($run->period_month)),
            'run'       => $run,
            'payslips'  => $payslips,
            'lineItems' => $lineItems,
            'lookups'   => new Lookups(),
        ]);
    }

    /** Manager adjustment to a payslip's basic salary before finalizing. */
    public function updatePayslip(int $id, int $payslipId)
    {
        $payslips = new PayslipModel();
        $payslip  = $payslips->find($payslipId);
        $run      = (new PayrollRunModel())->find($id);

        if ($payslip === null || $run === null || $run->status !== 'draft') {
            return redirect()->back()->with('error', 'This payroll run can no longer be edited.');
        }

        $payslips->update($payslipId, ['basic_salary' => parse_money($this->request->getPost('basic_salary'))]);
        $payslips->recalculate($payslipId);

        return redirect()->to('payroll/' . $id)->with('success', 'Payslip updated.');
    }

    public function addLineItem(int $id, int $payslipId)
    {
        $payslips = new PayslipModel();
        $payslip  = $payslips->find($payslipId);
        $run      = (new PayrollRunModel())->find($id);

        if ($payslip === null || $run === null || $run->status !== 'draft') {
            return redirect()->back()->with('error', 'This payroll run can no longer be edited.');
        }

        $lineItems = new PayslipLineItemModel();
        $data = [
            'payslip_id' => $payslipId,
            'type'       => $this->request->getPost('type'),
            'label'      => $this->request->getPost('label'),
            'amount'     => parse_money($this->request->getPost('amount')),
        ];

        if (! $lineItems->insert($data)) {
            return $this->redirectWithModelErrors($lineItems);
        }

        $payslips->recalculate($payslipId);

        return redirect()->to('payroll/' . $id)->with('success', 'Line item added.');
    }

    public function deleteLineItem(int $id, int $payslipId, int $lineItemId)
    {
        $run = (new PayrollRunModel())->find($id);

        if ($run === null || $run->status !== 'draft') {
            return redirect()->back()->with('error', 'This payroll run can no longer be edited.');
        }

        (new PayslipLineItemModel())->delete($lineItemId);
        (new PayslipModel())->recalculate($payslipId);

        return redirect()->to('payroll/' . $id)->with('success', 'Line item removed.');
    }

    /** Locks the numbers — draft -> finalized. No further payslip edits after this. */
    public function finalize(int $id)
    {
        $runs = new PayrollRunModel();
        $run  = $runs->find($id);

        if ($run === null || $run->status !== 'draft') {
            return redirect()->back()->with('error', 'Only a draft run can be finalized.');
        }

        $runs->update($id, ['status' => 'finalized', 'finalized_at' => date('Y-m-d H:i:s')]);
        $this->logActivity('payroll', 'Finalized the payroll run for ' . date('F Y', strtotime($run->period_month)) . '.');

        return redirect()->to('payroll/' . $id)->with('success', 'Payroll run finalized.');
    }

    /**
     * Record-keeping only — no money moves through this app. The manager
     * has already paid staff through their own existing means; this just
     * notes that it happened and (optionally) how, mirroring how folio
     * payments record a reference without this app handling disbursement.
     */
    public function markPaid(int $id)
    {
        $runs = new PayrollRunModel();
        $run  = $runs->find($id);

        if ($run === null || $run->status !== 'finalized') {
            return redirect()->back()->with('error', 'Only a finalized run can be marked paid.');
        }

        $runs->update($id, [
            'status'             => 'paid',
            'paid_at'            => date('Y-m-d H:i:s'),
            'payment_reference'  => $this->request->getPost('payment_reference') ?: null,
        ]);

        $this->logActivity('payroll', 'Marked the payroll run for ' . date('F Y', strtotime($run->period_month)) . ' as paid.');

        return redirect()->to('payroll/' . $id)->with('success', 'Payroll run marked as paid.');
    }

    /** Same service('pdfService')->renderHtml() call every other PDF in this app already uses (invoices, corporate statements). */
    public function payslipPdf(int $id, int $payslipId)
    {
        $payslip = (new PayslipModel())->find($payslipId);
        $run     = (new PayrollRunModel())->find($id);

        if ($payslip === null || $run === null) {
            return redirect()->to('payroll')->with('error', 'Payslip not found.');
        }

        $lineItems = (new PayslipLineItemModel())->forPayslip($payslipId);

        // Live lookup, not frozen on the payslip — role/employee ID/SSNIT/
        // Ghana Card/bank details aren't payroll figures the way
        // basic_salary/net_pay are, so it's fine (and more useful) to
        // reflect the staff member's current record rather than snapshot
        // it. Falls back gracefully if the user was since removed
        // (payslips.user_id is FK ... SET NULL).
        $staffUser = $payslip->user_id ? (new UserModel())->find($payslip->user_id) : null;

        $html = view('payroll/payslip_pdf', [
            'payslip'   => $payslip,
            'run'       => $run,
            'lineItems' => $lineItems,
            'lookups'   => new Lookups(),
            'tenant'    => service('tenantContext')->getTenant(),
            'staffUser' => $staffUser,
            'issuedAt'  => date('Y-m-d H:i'),
        ]);

        $pdf = service('pdfService')->renderHtml($html);

        return $this->response
            ->setHeader('Content-Type', 'application/pdf')
            ->setHeader('Content-Disposition', 'inline; filename="payslip-' . $payslip->id . '.pdf"')
            ->setBody($pdf);
    }
}
