<?php
/**
 * PaymentRequestsController — Slice 8Z-s · Slice D2
 *
 * Endpoints:
 *   /PaymentRequests/view/{id}       GET  — PR detail (any role with 'view')
 *   /PaymentRequests/raiseDialogAj   POST — Procurement: fetch term availability for the raise modal
 *   /PaymentRequests/raiseAj         POST — Procurement: create SUBMITTED PR + lines
 *   /PaymentRequests/pcQueue         GET  — Projects Coordinator queue
 *   /PaymentRequests/pcConfirmAj     POST — PC confirms and pushes to PM
 *   /PaymentRequests/pmQueue         GET  — Project Manager queue
 *   /PaymentRequests/pmReviewAj      POST — PM approves and pushes to FM
 *   /PaymentRequests/fmQueue         GET  — Finance Manager queue
 *   /PaymentRequests/fmScheduleAj    POST — FM optionally attaches a payment_schedule_id and pushes to Accountant
 *   /PaymentRequests/acctQueue       GET  — Accountant queue
 *   /PaymentRequests/acctRecordAj    POST — Accountant records disbursement (ref, optional journal id) → PAID
 *   /PaymentRequests/rejectAj        POST — reject at current rung (bounces to REJECTED with rollback)
 *   /PaymentRequests/cancelAj        POST — raiser or admin cancels
 *   /PaymentRequests/forLpoAj        GET  — JSON list of PRs on an LPO (used by Lpos/view card)
 */
class PaymentRequestsController extends Controller
{
    /* ── Detail + JSON helpers ────────────────────────────────────────── */

    public function viewAction($id = 0)
    {
        $id = (int)$id;
        if ($id <= 0) { $this->fb('Missing PR id.'); return; }
        $m  = new PaymentRequests();
        $pr = $m->find($id);
        if (empty($pr)) { $this->fb('Payment Request not found.'); return; }
        $lines = $m->linesFor($id);
        $group = strtolower(trim($this->currentUserGroup()));
        $canPc   = in_array($group, ['projects coordinator','administrator'], true) && $pr['next_check_status'] === 'PC_CONFIRM';
        $canPm   = in_array($group, ['project manager','administrator'], true)      && $pr['next_check_status'] === 'PM_REVIEW';
        $canFm   = in_array($group, ['finance manager','administrator'], true)      && $pr['next_check_status'] === 'FM_SCHEDULE';
        $canAcct = in_array($group, ['accountant','administrator'], true)           && $pr['next_check_status'] === 'ACCT_RECORD';
        $canReject = ($canPc || $canPm || $canFm || $canAcct);
        $canCancel = in_array($pr['status'], ['SUBMITTED','PC_CONFIRMED','PM_REVIEWED','SCHEDULED'], true)
                     && ((int)$pr['raised_by'] === $this->currentUserId() || $group === 'administrator');

        $this->view->setLayout($this->roleLayout());
        $this->view->render('PaymentRequests/view', [
            'page_title' => 'PR ' . $pr['pr_no'],
            'pr'         => $pr,
            'lines'      => $lines,
            'can_pc'     => $canPc,
            'can_pm'     => $canPm,
            'can_fm'     => $canFm,
            'can_acct'   => $canAcct,
            'can_reject' => $canReject,
            'can_cancel' => $canCancel,
        ]);
    }

    public function forLpoAjAction()
    {
        header('Content-Type: application/json');
        try {
            $lpoId = (int)($_GET['lpo_id'] ?? $_POST['lpo_id'] ?? 0);
            if ($lpoId <= 0) throw new InvalidArgumentException('lpo_id required.');
            echo json_encode(['ok' => true, 'rows' => (new PaymentRequests())->forLpo($lpoId)]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /* ── Raise (Procurement) ──────────────────────────────────────────── */

    public function raiseDialogAjAction()
    {
        header('Content-Type: application/json');
        try {
            $lpoId = (int)($_GET['lpo_id'] ?? $_POST['lpo_id'] ?? 0);
            if ($lpoId <= 0) throw new InvalidArgumentException('lpo_id required.');
            $this->assertProcurementOrAdmin();
            $terms = (new LpoPaymentTerms())->availabilityForLpo($lpoId);
            $db = new DB();
            $lpo = $db->mySelect(
                "SELECT lpo_no, cash_out_status, currency, amount, receipt_status, issued_at
                   FROM lpos WHERE c0d = :id AND deleted = 0 AND lpo_type='SUPPLIER'",
                [':id' => $lpoId]
            );
            if (empty($lpo)) throw new RuntimeException('SUPPLIER LPO not found.');
            echo json_encode(['ok' => true, 'lpo' => $lpo[0], 'terms' => $terms]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function raiseAjAction()
    {
        header('Content-Type: application/json');
        try {
            $this->assertProcurementOrAdmin();
            $lines = json_decode((string)($_POST['lines'] ?? '[]'), true);
            if (!is_array($lines)) $lines = [];
            $prId = (new PaymentRequests())->raise([
                'lpo_id'                => (int)($_POST['lpo_id'] ?? 0),
                'notes'                 => (string)($_POST['notes'] ?? ''),
                'supplier_invoice_no'   => (string)($_POST['supplier_invoice_no']   ?? ''),
                'supplier_invoice_date' => (string)($_POST['supplier_invoice_date'] ?? ''),
                'lines'                 => $lines,
            ], $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode(['ok' => true, 'pr_id' => $prId]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /* ── Queues ───────────────────────────────────────────────────────── */

    public function pcQueueAction()      { $this->renderQueue('PC_CONFIRM',    'PC to confirm',          'projects coordinator'); }
    public function pmQueueAction()      { $this->renderQueue('PM_REVIEW',     'PM to review',           'project manager'); }
    public function fmQueueAction()      { $this->renderQueue('FM_SCHEDULE',   'FM to schedule',         'finance manager'); }
    public function acctQueueAction()    { $this->renderQueue('ACCT_RECORD',   'Accountant to record',   'accountant'); }

    /** Slice 8Z-u · Procurement roll-up: my raised PRs, any status. */
    public function myAction()
    {
        $status = (string)($_GET['status'] ?? '');
        $rows   = (new PaymentRequests())->forRaiser($this->currentUserId(), $status);
        $this->view->setLayout($this->roleLayout());
        $this->view->render('PaymentRequests/my', [
            'page_title' => 'My Payment Requests',
            'rows'       => $rows,
            'status'     => $status,
        ]);
    }

    private function renderQueue(string $rung, string $title, string $roleLower): void
    {
        $group = strtolower(trim($this->currentUserGroup()));
        if ($group !== $roleLower && $group !== 'administrator') { $this->fb('Access denied.'); return; }
        $rows = (new PaymentRequests())->queueForRung($rung);
        $this->view->setLayout($this->roleLayout());
        $this->view->render('PaymentRequests/queue', [
            'page_title' => 'Payment Requests · ' . $title,
            'rows'       => $rows,
            'rung'       => $rung,
            'title'      => $title,
        ]);
    }

    /* ── Rung actions ─────────────────────────────────────────────────── */

    public function pcConfirmAjAction()
    {
        header('Content-Type: application/json');
        try {
            $this->assertRole(['projects coordinator','administrator']);
            $prId = (int)($_POST['pr_id'] ?? 0);
            $note = (string)($_POST['note'] ?? '');
            if ($prId <= 0) throw new InvalidArgumentException('pr_id required.');
            (new PaymentRequests())->pcConfirm($prId, $note, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode(['ok' => true]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function pmReviewAjAction()
    {
        header('Content-Type: application/json');
        try {
            $this->assertRole(['project manager','administrator']);
            $prId = (int)($_POST['pr_id'] ?? 0);
            $note = (string)($_POST['note'] ?? '');
            if ($prId <= 0) throw new InvalidArgumentException('pr_id required.');
            (new PaymentRequests())->pmReview($prId, $note, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode(['ok' => true]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function fmScheduleAjAction()
    {
        header('Content-Type: application/json');
        try {
            $this->assertRole(['finance manager','administrator']);
            $prId = (int)($_POST['pr_id'] ?? 0);
            $sid  = !empty($_POST['payment_schedule_id']) ? (int)$_POST['payment_schedule_id'] : null;
            $note = (string)($_POST['note'] ?? '');
            if ($prId <= 0) throw new InvalidArgumentException('pr_id required.');
            (new PaymentRequests())->fmSchedule($prId, $sid, $note, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode(['ok' => true]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /**
     * Slice 8Z-w · Slice D4 — Batch multiple PRs into a single Payment Schedule.
     * FM selects PRs at FM_SCHEDULE rung from the queue, submits array of pr_ids.
     * Creates a schedule directly in READY_TO_EXECUTE (PRs were pre-approved individually).
     */
    public function batchToScheduleAjAction()
    {
        header('Content-Type: application/json');
        try {
            $this->assertRole(['finance manager','administrator']);
            $prIds = json_decode((string)($_POST['pr_ids'] ?? '[]'), true);
            if (!is_array($prIds) || empty($prIds)) throw new InvalidArgumentException('At least one PR must be selected.');
            $scheduledDate = trim((string)($_POST['scheduled_date'] ?? ''));
            $notes         = trim((string)($_POST['notes'] ?? ''));
            if (!class_exists('PaymentSchedules')) require_once ROOT . '/app/models/PaymentSchedules.php';
            $r = (new PaymentSchedules())->createFromPrs(
                $prIds, $scheduledDate, $notes,
                $this->currentUserId(), $_SESSION['full_name'] ?? '',
                strtoupper(trim((string)$this->currentUserGroup()))
            );
            if (empty($r['ok'])) { echo json_encode($r); return; }
            // Ping Accountant group that a batch is ready to execute
            try {
                if (!class_exists('Notifications')) require_once ROOT . '/app/models/Notifications.php';
                (new Notifications())->notifyGroup(
                    'Accountant', 'SCHEDULE_READY_TO_EXECUTE',
                    'Batch PR schedule ready: ' . $r['schedule_no'],
                    $r['added'] . ' Payment Request' . ($r['added'] === 1 ? '' : 's') . ' bundled &mdash; execute + record disbursement to close all.',
                    'PaymentSchedules/view/' . (int)$r['id'], 'payment_schedule', (int)$r['id'],
                    $this->currentUserId(), $_SESSION['full_name'] ?? ''
                );
            } catch (\Throwable $e) { /* best-effort */ }
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function acctRecordAjAction()
    {
        header('Content-Type: application/json');
        try {
            $this->assertRole(['accountant','administrator']);
            $prId = (int)($_POST['pr_id'] ?? 0);
            $ref  = trim((string)($_POST['disburse_ref'] ?? ''));
            $jeId = !empty($_POST['journal_entry_id']) ? (int)$_POST['journal_entry_id'] : null;
            $note = (string)($_POST['note'] ?? '');
            if ($prId <= 0)  throw new InvalidArgumentException('pr_id required.');
            if ($ref === '') throw new InvalidArgumentException('Disbursement reference is required.');
            (new PaymentRequests())->acctRecord($prId, $ref, $jeId, $note, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode(['ok' => true]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function rejectAjAction()
    {
        header('Content-Type: application/json');
        try {
            $prId   = (int)($_POST['pr_id'] ?? 0);
            $stage  = trim((string)($_POST['stage']  ?? ''));
            $reason = trim((string)($_POST['reason'] ?? ''));
            if ($prId <= 0)  throw new InvalidArgumentException('pr_id required.');
            if ($stage === '')  throw new InvalidArgumentException('stage required.');
            if ($reason === '') throw new InvalidArgumentException('Please give a reason for rejection.');
            // Any of the four rung-role-groups (or admin) can reject at their rung.
            $group = strtolower(trim($this->currentUserGroup()));
            $allowedByStage = [
                'PC_CONFIRM'  => ['projects coordinator','administrator'],
                'PM_REVIEW'   => ['project manager','administrator'],
                'FM_SCHEDULE' => ['finance manager','administrator'],
                'ACCT_RECORD' => ['accountant','administrator'],
            ];
            if (!isset($allowedByStage[$stage]) || !in_array($group, $allowedByStage[$stage], true)) {
                throw new RuntimeException('You do not have permission to reject at ' . $stage . '.');
            }
            (new PaymentRequests())->reject($prId, $stage, $reason, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode(['ok' => true]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function cancelAjAction()
    {
        header('Content-Type: application/json');
        try {
            $prId = (int)($_POST['pr_id'] ?? 0);
            if ($prId <= 0) throw new InvalidArgumentException('pr_id required.');
            $pr = (new PaymentRequests())->find($prId);
            if (empty($pr)) throw new RuntimeException('PR not found.');
            $group = strtolower(trim($this->currentUserGroup()));
            if ((int)$pr['raised_by'] !== $this->currentUserId() && $group !== 'administrator') {
                throw new RuntimeException('Only the raiser or an administrator can cancel this PR.');
            }
            (new PaymentRequests())->cancel($prId, $this->currentUserId());
            echo json_encode(['ok' => true]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /* ── Guards + feedback ─────────────────────────────────────────────── */

    private function assertProcurementOrAdmin(): void
    {
        $this->assertRole(['procurement','administrator']);
    }

    private function assertRole(array $allowed): void
    {
        $g = strtolower(trim($this->currentUserGroup()));
        if (!in_array($g, $allowed, true)) throw new RuntimeException('Access denied.');
    }

    private function fb(string $msg): void
    {
        $this->view->setLayout($this->roleLayout());
        $this->view->render('generals/fb_page', [
            'page_title' => 'Payment Requests',
            'fb_msg'     => '<div class="alert alert-warning">' . htmlspecialchars($msg) . '</div>',
        ]);
    }
}
