<?php
/**
 * PaymentSchedulesController — Slice 8Z-l
 *
 * Endpoints:
 *   /PaymentSchedules              index  — list all schedules
 *   /PaymentSchedules/queue        — Accountant's BOOKED LPO pool + build-schedule surface
 *   /PaymentSchedules/view/{id}    — schedule detail (LPOs grouped by method + payee)
 *   /PaymentSchedules/createAj     POST — create + add items + auto-submit chain (one shot)
 *   /PaymentSchedules/addLposAj    POST — add more LPOs to a DRAFT schedule
 *   /PaymentSchedules/removeLpoAj  POST — remove an LPO from a DRAFT schedule
 *   /PaymentSchedules/submitAj     POST — submit a DRAFT schedule into PAYMENT_SCHEDULE_APPROVAL
 *
 * FM-created schedules auto-approve the FM step (creator ≠ approver) — handled in the model.
 */
class PaymentSchedulesController extends Controller
{
    public function indexAction()
    {
        $status = (string)($_GET['status'] ?? '');
        $rows = (new PaymentSchedules())->list($status);
        $this->view->setLayout($this->roleLayout());
        $this->view->render('PaymentSchedules/index', [
            'page_title'    => 'Payment schedules',
            'rows'          => $rows,
            'filter_status' => $status,
        ]);
    }

    /**
     * Accountant's build surface — pool of BOOKED LPOs (not yet on any schedule) with checkboxes.
     * Filters: method + currency.
     */
    public function queueAction()
    {
        $filters = [
            'currency' => (string)($_GET['currency'] ?? ''),
            'method'   => (string)($_GET['method'] ?? ''),
        ];
        $rows = (new PaymentSchedules())->bookedLposFor($filters);
        $this->view->setLayout($this->roleLayout());
        $this->view->render('PaymentSchedules/queue', [
            'page_title'    => 'Build payment schedule',
            'rows'          => $rows,
            'filter_method' => $filters['method'],
        ]);
    }

    public function viewAction($id = 0)
    {
        $id = (int)$id;
        $m  = new PaymentSchedules();
        $s  = $m->byId($id);
        if (!$s) {
            $this->view->setLayout($this->roleLayout());
            $this->view->render('generals/fb_page', ['fb_msg' => '<div class="alert alert-danger">Payment schedule not found.</div>']);
            return;
        }
        $items = $m->itemsFor($id);
        $attachments = $m->attachmentsFor($id);

        // Load the approval strip if in-flight or done.
        $approval = []; $steps = []; $history = [];
        if ((int)$s['approval_id'] > 0) {
            $ac = new ApprovalChain();
            $approval = $ac->approvalById((int)$s['approval_id']) ?: [];
            if (!empty($approval)) {
                $steps   = $ac->stepsForChain((int)$approval['chain_id']);
                $history = $ac->historyFor((int)$s['approval_id']);
            }
        }

        // Group items by method for the batch view (FM sees at a glance: X on M-Pesa, Y on CRDB, ...)
        $groups = [];
        foreach ($items as $it) {
            $key = $it['method'] ?: 'UNSET';
            if (!isset($groups[$key])) $groups[$key] = ['method' => $key, 'items' => [], 'total' => 0.0, 'count' => 0];
            $groups[$key]['items'][] = $it;
            $groups[$key]['total']   += (float)$it['amount'];
            $groups[$key]['count']++;
        }

        $group = strtolower($this->currentUserGroup());
        $canEdit = in_array($group, ['accountant','finance manager','administrator'], true)
                   && $s['status'] === 'DRAFT'
                   && (int)$s['created_by'] === $this->currentUserId();

        $this->view->setLayout($this->roleLayout());
        $this->view->render('PaymentSchedules/view', [
            'page_title' => 'Payment schedule · ' . $s['schedule_no'],
            'schedule'   => $s,
            'items'      => $items,
            'groups'     => array_values($groups),
            'approval'   => $approval,
            'steps'      => $steps,
            'history'    => $history,
            'can_edit'   => $canEdit,
            'attachments'=> $attachments,
        ]);
    }

    /** Create + auto-submit in one action from the queue surface. */
    public function createAjAction()
    {
        header('Content-Type: application/json');
        try {
            $ids   = $_POST['lpo_ids'] ?? [];
            if (!is_array($ids)) $ids = array_filter(array_map('trim', explode(',', (string)$ids)));
            $sched = (string)($_POST['scheduled_date'] ?? '');
            $notes = (string)($_POST['notes'] ?? '');
            if (empty($ids)) throw new InvalidArgumentException('Pick at least one LPO.');

            $m = new PaymentSchedules();
            $r = $m->createFromLpos(
                array_map('intval', $ids), $sched, $notes,
                $this->currentUserId(), $_SESSION['full_name'] ?? '', $_SESSION['user_group_name'] ?? ''
            );
            if (empty($r['ok'])) { echo json_encode($r); return; }

            // Immediate submit — the queue button is "Create + submit for approval" (single click).
            $sub = $m->submit((int)$r['id'], $this->currentUserId(), $_SESSION['full_name'] ?? '', $_SESSION['user_group_name'] ?? '');
            if (empty($sub['ok'])) { echo json_encode(['ok' => false, 'msg' => 'Schedule created but submit failed: ' . ($sub['msg'] ?? '')]); return; }

            echo json_encode(['ok' => true, 'id' => (int)$r['id'], 'schedule_no' => $r['schedule_no'], 'added' => (int)$r['added'], 'skipped' => (int)$r['skipped'], 'msg' => 'Schedule ' . $r['schedule_no'] . ' submitted (' . $r['added'] . ' items).']);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function addLposAjAction()
    {
        header('Content-Type: application/json');
        try {
            $sid = (int)($_POST['schedule_id'] ?? 0);
            $ids = $_POST['lpo_ids'] ?? [];
            if (!is_array($ids)) $ids = array_filter(array_map('trim', explode(',', (string)$ids)));
            if ($sid <= 0)     throw new InvalidArgumentException('schedule_id required.');
            if (empty($ids))   throw new InvalidArgumentException('Pick at least one LPO.');
            $r = (new PaymentSchedules())->addLpos($sid, array_map('intval', $ids), $this->currentUserId());
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function removeLpoAjAction()
    {
        header('Content-Type: application/json');
        try {
            $sid = (int)($_POST['schedule_id'] ?? 0);
            $lid = (int)($_POST['lpo_id']      ?? 0);
            if ($sid <= 0 || $lid <= 0) throw new InvalidArgumentException('schedule_id + lpo_id required.');
            $r = (new PaymentSchedules())->removeLpo($sid, $lid);
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function submitAjAction()
    {
        header('Content-Type: application/json');
        try {
            $sid = (int)($_POST['schedule_id'] ?? 0);
            if ($sid <= 0) throw new InvalidArgumentException('schedule_id required.');
            $r = (new PaymentSchedules())->submit($sid, $this->currentUserId(), $_SESSION['full_name'] ?? '', $_SESSION['user_group_name'] ?? '');
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /* ── Slice 8Z-m · FM proposal-review endpoints ───────────────────── */

    public function updateItemAmountAjAction()
    {
        header('Content-Type: application/json');
        try {
            $sid    = (int)   ($_POST['schedule_id'] ?? 0);
            $lid    = (int)   ($_POST['lpo_id']      ?? 0);
            $amount = (float) ($_POST['amount']      ?? 0);
            $note   = (string)($_POST['comment']     ?? '');
            if ($sid <= 0 || $lid <= 0) throw new InvalidArgumentException('schedule_id + lpo_id required.');
            $r = (new PaymentSchedules())->updateItemAmount($sid, $lid, $amount, $note, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function excludeItemAjAction()
    {
        header('Content-Type: application/json');
        try {
            $sid  = (int)   ($_POST['schedule_id'] ?? 0);
            $lid  = (int)   ($_POST['lpo_id']      ?? 0);
            $note = (string)($_POST['comment']     ?? '');
            if ($sid <= 0 || $lid <= 0) throw new InvalidArgumentException('schedule_id + lpo_id required.');
            $r = (new PaymentSchedules())->excludeItem($sid, $lid, $note, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function applyGroupCapAjAction()
    {
        header('Content-Type: application/json');
        try {
            $sid    = (int)   ($_POST['schedule_id'] ?? 0);
            $ptype  = strtoupper((string)($_POST['payee_type'] ?? 'STAFF'));
            $pid    = (int)   ($_POST['payee_id']    ?? 0);
            $method = (string)($_POST['method']      ?? '');
            $cap    = (float) ($_POST['cap']         ?? 0);
            $note   = (string)($_POST['comment']     ?? '');
            if ($sid <= 0 || $pid <= 0 || $method === '') throw new InvalidArgumentException('schedule_id + payee_id + method required.');
            $r = (new PaymentSchedules())->applyGroupCap($sid, $ptype, $pid, $method, $cap, $note, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function recordDisbursementAjAction()
    {
        header('Content-Type: application/json');
        try {
            $sid   = (int)($_POST['schedule_id'] ?? 0);
            $batch = (string)($_POST['batch_ref'] ?? '');
            $perLn = $_POST['per_line'] ?? [];
            if (!is_array($perLn)) $perLn = [];
            $refs = ['batch' => $batch, 'per_line' => array_map(function ($v) { return trim((string)$v); }, $perLn)];
            if ($sid <= 0) throw new InvalidArgumentException('schedule_id required.');

            // Slice 8Z-n · proof required for batch ref (audit rule).
            $ps = new PaymentSchedules();
            $batchProof   = $_FILES['batch_proof']   ?? null;
            $lineProofs   = $_FILES['line_proofs']   ?? null; // multi-file per LPO
            if ($batch !== '' && (!$batchProof || empty($batchProof['tmp_name']) || !is_uploaded_file($batchProof['tmp_name']))) {
                throw new InvalidArgumentException('Batch proof file is required when batch reference is set (audit trail).');
            }

            $r = $ps->recordDisbursement($sid, $refs, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            if (empty($r['ok'])) { echo json_encode($r); return; }

            // Store batch proof (if uploaded)
            if ($batchProof && !empty($batchProof['tmp_name']) && is_uploaded_file($batchProof['tmp_name'])) {
                $ps->addAttachment($sid, 'BATCH', $batchProof, null, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            }
            // Store per-line proofs (structure: $_FILES['line_proofs']['tmp_name'][lpo_id] etc.)
            if (is_array($lineProofs) && !empty($lineProofs['tmp_name']) && is_array($lineProofs['tmp_name'])) {
                foreach ($lineProofs['tmp_name'] as $lpoId => $tmp) {
                    if (empty($tmp) || !is_uploaded_file($tmp)) continue;
                    $file = [
                        'tmp_name' => $tmp,
                        'name'     => $lineProofs['name'][$lpoId]     ?? 'proof',
                        'type'     => $lineProofs['type'][$lpoId]     ?? '',
                        'error'    => $lineProofs['error'][$lpoId]    ?? UPLOAD_ERR_OK,
                        'size'     => $lineProofs['size'][$lpoId]     ?? 0,
                    ];
                    try { $ps->addAttachment($sid, 'LINE', $file, (int)$lpoId, $this->currentUserId(), $_SESSION['full_name'] ?? ''); }
                    catch (\Throwable $e) { /* skip failed line-proof, don't break the batch */ }
                }
            }
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /** Slice 8Z-o · FM Prepare — allocates the instrument + planned date, flips to READY_TO_EXECUTE.
     *  Slice 8Z-p · optional prep attachment (cheque scan, signed letter, screenshot). */
    public function preparePaymentAjAction()
    {
        header('Content-Type: application/json');
        try {
            $sid = (int)($_POST['schedule_id'] ?? 0);
            if ($sid <= 0) throw new InvalidArgumentException('schedule_id required.');
            $prepFile = $_FILES['prep_proof'] ?? null;
            $r = (new PaymentSchedules())->preparePayment($sid, [
                'disburse_instrument'     => (string)($_POST['disburse_instrument'] ?? ''),
                'disburse_instrument_ref' => (string)($_POST['disburse_instrument_ref'] ?? ''),
                'disburse_planned_date'   => (string)($_POST['disburse_planned_date'] ?? ''),
                'disburse_prep_notes'     => (string)($_POST['disburse_prep_notes'] ?? ''),
            ], $this->currentUserId(), $_SESSION['full_name'] ?? '', $prepFile);
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /** Slice 8Z-n · stream a proof attachment (audit trail viewer). */
    public function proofDownloadAction($attId = 0)
    {
        $attId = (int)$attId;
        if ($attId <= 0) { http_response_code(400); echo 'Bad request.'; return; }
        $att = (new PaymentSchedules())->attachmentById($attId);
        if (empty($att))                       { http_response_code(404); echo 'Not found.'; return; }
        $abs = ROOT . '/' . ltrim((string)$att['file_path'], '/');
        if (!is_file($abs))                    { http_response_code(404); echo 'File missing on disk.'; return; }
        $mime = (string)($att['mime'] ?: 'application/octet-stream');
        $name = (string)($att['original_name'] ?: basename($abs));
        header('Content-Type: ' . $mime);
        header('Content-Length: ' . (int)filesize($abs));
        header('Content-Disposition: inline; filename="' . str_replace('"', '', $name) . '"');
        readfile($abs);
        exit();
    }
}
