<?php
class LposController extends Controller
{
    public function indexAction()
    {
        $m    = new Lpos();
        $rows = $m->listForRole(
            $this->currentUserGroup(),
            $this->currentUserId(),
            (string)($_GET['status'] ?? '')
        );
        $this->view->setLayout($this->roleLayout());
        $this->view->render('Lpos/index', [
            'page_title' => 'LPOs · unified',
            'rows'       => $rows,
            'types'      => Lpos::TYPES(),
            'status'     => (string)($_GET['status'] ?? ''),
        ]);
    }

    public function viewAction($id = 0)
    {
        $m = new Lpos();
        $l = $m->byId((int)$id);
        if (!$l) {
            $this->view->setLayout($this->roleLayout());
            $this->view->render('generals/fb_page', ['fb_msg' => '<div class="alert alert-danger">LPO not found.</div>']);
            return;
        }
        $ac       = new ApprovalChain();
        $approval = ((int)$l['approval_id'] > 0) ? $ac->approvalById((int)$l['approval_id']) : [];
        $steps    = !empty($approval) ? $ac->stepsForChain((int)$approval['chain_id']) : [];
        $history  = !empty($approval) ? $ac->historyFor((int)$l['approval_id']) : [];

        $pvm = new PaymentVouchers();
        $pvs = $pvm->forLpo((int)$id);
        $group     = strtolower($this->currentUserGroup());
        $canPrepPv = in_array($group, ['accountant','administrator'], true)
                     && in_array($l['cash_out_status'], ['APPROVED','ISSUED'], true)
                     && !array_filter($pvs, fn($p) => in_array($p['status'], ['DRAFT','SCHEDULED','PAID'], true));

        // GRN roll-up: line-level qty ordered/received, plus GRN list
        $m      = new Lpos();
        $items  = $m->itemsFor((int)$id);
        $gm     = new GoodsGrn();
        $grns   = ($l['lpo_type'] === 'SUPPLIER') ? $gm->forLpo((int)$id) : [];
        $canReceive = ($l['lpo_type'] === 'SUPPLIER')
                      && in_array($l['cash_out_status'], ['APPROVED','ISSUED','PAID'], true)
                      && $l['receipt_status'] !== 'FULLY_RECEIVED'
                      && in_array($group, ['stores','procurement','administrator'], true);

        // Slice 8Z-k · disburse plan + account (visible from Accountant Prepare onwards)
        $prefAccount = []; $disburseAccount = [];
        if (!empty($l['preferred_account_id']) || !empty($l['disburse_account_id'])) {
            if (!class_exists('PayeeAccounts')) require_once ROOT . '/app/models/PayeeAccounts.php';
            $pa = new PayeeAccounts();
            if (!empty($l['preferred_account_id'])) $prefAccount    = $pa->byId((int)$l['preferred_account_id']);
            if (!empty($l['disburse_account_id'])) $disburseAccount = $pa->byId((int)$l['disburse_account_id']);
        }
        $canDisburse = in_array($group, ['accountant','finance manager','administrator'], true)
                       && in_array($l['cash_out_status'], ['APPROVED','BOOKED'], true)
                       && !empty($l['disburse_method'])
                       && in_array($l['lpo_type'], Lpos::INTERNAL_CASH_TYPES, true);

        // Slice 8Z-s · Slice D2 — Supplier LPO Payment Requests
        $paymentRequests = []; $termAvailability = []; $canRaisePr = false;
        if ((string)$l['lpo_type'] === 'SUPPLIER') {
            $paymentRequests  = (new PaymentRequests())->forLpo((int)$id);
            $termAvailability = (new LpoPaymentTerms())->availabilityForLpo((int)$id);
            $anyClaimable = false;
            foreach ($termAvailability as $ta) { if ((float)$ta['claimable_now'] > 0.005) { $anyClaimable = true; break; } }
            $canRaisePr = in_array($group, ['procurement','administrator'], true)
                          && in_array($l['cash_out_status'], ['APPROVED','ISSUED','PAID'], true)
                          && $anyClaimable;
        }

        $this->view->setLayout($this->roleLayout());
        $this->view->render('Lpos/view', [
            'page_title'       => $l['lpo_no'] . ' · ' . (Lpos::TYPES()[$l['lpo_type']]['label'] ?? $l['lpo_type']),
            'l'                => $l,
            'types'            => Lpos::TYPES(),
            'approval'         => $approval,
            'steps'            => $steps,
            'history'          => $history,
            'pvs'              => $pvs,
            'can_prep_pv'      => $canPrepPv,
            'lpo_items'        => $items,
            'grns'             => $grns,
            'can_receive'      => $canReceive,
            'pref_account'     => $prefAccount,
            'disburse_account' => $disburseAccount,
            'can_disburse'     => $canDisburse,
            'payment_requests'  => $paymentRequests,
            'term_availability' => $termAvailability,
            'can_raise_pr'      => $canRaisePr,
        ]);
    }

    /**
     * Slice 8Z-k · Mark an APPROVED cash LPO as disbursed. Captures ref (transaction id / cheque #)
     * + timestamp + actor. For PETTY_CASH prefer PettyCashBox::disburse (also updates the tin
     * balance); use this endpoint for MOBILE_MONEY / BANK_TRANSFER / CHEQUE / CASH_HANDOVER.
     */
    public function markDisbursedAjAction()
    {
        header('Content-Type: application/json');
        try {
            $lpoId = (int)($_POST['lpo_id'] ?? 0);
            $ref   = (string)($_POST['disburse_ref'] ?? '');
            if ($lpoId <= 0) throw new InvalidArgumentException('lpo_id required.');
            $r = (new Lpos())->markDisbursed($lpoId, $ref, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            if (!empty($r['ok'])) {
                try {
                    if (!class_exists('Notifications')) require_once ROOT . '/app/models/Notifications.php';
                    $db = new DB();
                    $reqRs = $db->mySelect("SELECT requested_by, disburse_method FROM lpos WHERE c0d = :id", [':id' => $lpoId]);
                    $reqId = (int)($reqRs[0]['requested_by'] ?? 0);
                    if ($reqId > 0) {
                        (new Notifications())->notify(
                            $reqId, 'CASH_DISBURSED',
                            'Cash sent: ' . ($r['lpo_no'] ?? ''),
                            'Disbursed via ' . strtolower(str_replace('_',' ', (string)($reqRs[0]['disburse_method'] ?? ''))) . ($ref !== '' ? ' · ref ' . $ref : ''),
                            'Lpos/view/' . $lpoId, 'lpo', $lpoId, $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()]); }
    }

    /**
     * Slice 8Z-q · Requester confirms cash actually arrived post-disbursement.
     * Only the original requester (or Admin/Finance Manager) can confirm.
     * Confirmation starts the 14-day retirement clock.
     */
    public function confirmReceiptAjAction()
    {
        header('Content-Type: application/json');
        try {
            $lpoId = (int)($_POST['lpo_id'] ?? 0);
            $note  = (string)($_POST['note'] ?? '');
            if ($lpoId <= 0) throw new InvalidArgumentException('lpo_id required.');

            $db  = new DB();
            $row = $db->mySelect("SELECT requested_by FROM lpos WHERE c0d = :id", [':id' => $lpoId]);
            if (empty($row)) throw new RuntimeException('LPO not found.');
            $role = strtolower(trim($this->currentUserGroup()));
            $isPriv = in_array($role, ['administrator','finance manager','accountant'], true);
            if ((int)$row[0]['requested_by'] !== $this->currentUserId() && !$isPriv) {
                throw new RuntimeException('Only the requester (or Finance) can confirm receipt.');
            }
            $r = (new Lpos())->confirmReceipt($lpoId, $note, $this->currentUserId(), $_SESSION['full_name'] ?? '', false);
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /* ── Slice 8Z-r · Slice D1 — Supplier LPO payment terms + Issue to supplier ─── */

    public function addPaymentTermAjAction()
    {
        header('Content-Type: application/json');
        try {
            $lpoId = (int)($_POST['lpo_id'] ?? 0);
            if ($lpoId <= 0) throw new InvalidArgumentException('lpo_id required.');
            $this->assertProcurementOrAdmin();
            $termId = (new LpoPaymentTerms())->add($lpoId, $this->collectTermFields(), $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode(['ok' => true, 'term_id' => $termId, 'summary' => (new LpoPaymentTerms())->summaryString($lpoId)]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function updatePaymentTermAjAction()
    {
        header('Content-Type: application/json');
        try {
            $termId = (int)($_POST['term_id'] ?? 0);
            if ($termId <= 0) throw new InvalidArgumentException('term_id required.');
            $this->assertProcurementOrAdmin();
            $m = new LpoPaymentTerms();
            $t = $m->find($termId);
            if (empty($t)) throw new RuntimeException('Term not found.');
            $m->update($termId, $this->collectTermFields());
            echo json_encode(['ok' => true, 'summary' => $m->summaryString((int)$t['lpo_id'])]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function removePaymentTermAjAction()
    {
        header('Content-Type: application/json');
        try {
            $termId = (int)($_POST['term_id'] ?? 0);
            if ($termId <= 0) throw new InvalidArgumentException('term_id required.');
            $this->assertProcurementOrAdmin();
            $m = new LpoPaymentTerms();
            $t = $m->find($termId);
            if (empty($t)) throw new RuntimeException('Term not found.');
            $m->remove($termId);
            echo json_encode(['ok' => true, 'summary' => $m->summaryString((int)$t['lpo_id'])]);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function issueToSupplierAjAction()
    {
        header('Content-Type: application/json');
        try {
            $lpoId = (int)($_POST['lpo_id'] ?? 0);
            if ($lpoId <= 0) throw new InvalidArgumentException('lpo_id required.');
            $this->assertProcurementOrAdmin();
            $file = $_FILES['attachment'] ?? null;
            $r = (new Lpos())->issueToSupplier($lpoId, [
                'issue_method' => (string)($_POST['issue_method'] ?? ''),
                'issue_ref'    => (string)($_POST['issue_ref']    ?? ''),
                'issue_note'   => (string)($_POST['issue_note']   ?? ''),
            ], $file, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /** Stream the signed-LPO / issuance attachment. */
    public function issueAttachmentDownloadAction($lpoId = 0)
    {
        $lpoId = (int)$lpoId;
        if ($lpoId <= 0) { http_response_code(400); echo 'Bad request.'; return; }
        $db = new DB();
        $r  = $db->mySelect(
            "SELECT issue_attachment_path, issue_attachment_original_name, issue_attachment_mime FROM lpos WHERE c0d = :id",
            [':id' => $lpoId]
        );
        if (empty($r) || empty($r[0]['issue_attachment_path'])) { http_response_code(404); echo 'Not found.'; return; }
        $abs = ROOT . '/' . ltrim((string)$r[0]['issue_attachment_path'], '/');
        if (!is_file($abs)) { http_response_code(404); echo 'File missing on disk.'; return; }
        header('Content-Type: ' . (string)($r[0]['issue_attachment_mime'] ?: 'application/octet-stream'));
        header('Content-Length: ' . (int)filesize($abs));
        header('Content-Disposition: inline; filename="' . str_replace('"', '', (string)($r[0]['issue_attachment_original_name'] ?: basename($abs))) . '"');
        readfile($abs);
        exit();
    }

    private function collectTermFields(): array
    {
        return [
            'trigger_type'        => (string)($_POST['trigger_type']        ?? 'ON_DELIVERY'),
            'label'               => (string)($_POST['label']               ?? ''),
            'percent'             => $_POST['percent']             ?? null,
            'fixed_amount'        => $_POST['fixed_amount']        ?? null,
            'net_days'            => $_POST['net_days']            ?? null,
            'retention_days'      => $_POST['retention_days']      ?? null,
            'milestone_condition' => (string)($_POST['milestone_condition'] ?? ''),
            'grn_percent_gate'    => $_POST['grn_percent_gate']    ?? null,
            'notes'               => (string)($_POST['notes']               ?? ''),
        ];
    }

    private function assertProcurementOrAdmin(): void
    {
        $group = strtolower(trim($this->currentUserGroup()));
        if (!in_array($group, ['procurement','administrator'], true)) {
            throw new RuntimeException('Only Procurement and Administrator can manage supplier LPO payment terms + issue.');
        }
    }

    /**
     * Slice 8a-2 · Draft-batch landing.
     * Shows the per-supplier preview built from awarded RFQ quotes on this project.
     * Procurement clicks "Create draft LPOs" → one LPO per vendor is materialised.
     */
    public function draftAction($projectId = 0)
    {
        $projectId = (int)$projectId;
        $db = new DB();
        $prj = $db->mySelect(
            "SELECT c0d, project_no, project_name, project_customer, currency, contract_value, project_type
               FROM c_projects WHERE c0d = :p AND deleted = 0",
            [':p' => $projectId]
        );
        if (empty($prj)) {
            $this->view->setLayout($this->roleLayout());
            $this->view->render('generals/fb_page', ['fb_msg' => '<div class="alert alert-danger">Project not found.</div>']);
            return;
        }
        $preview = (new Lpos())->draftPreviewForProject($projectId);

        $this->view->setLayout($this->roleLayout());
        $this->view->render('Lpos/draft', [
            'page_title' => 'Draft supplier LPOs · ' . ($prj[0]['project_no'] ?? ''),
            'project'    => $prj[0],
            'groups'     => $preview,
        ]);
    }

    public function draftBatchAjAction()
    {
        header('Content-Type: application/json');
        try {
            $projectId = (int)($_POST['project_id'] ?? 0);
            if ($projectId <= 0) throw new InvalidArgumentException('project_id required.');
            $res = (new Lpos())->draftBatchForProject(
                $projectId,
                (int)($_SESSION['user_id'] ?? 0),
                (string)($_SESSION['full_name'] ?? '')
            );
            $created = 0; $skipped = 0; $failed = 0;
            foreach ($res as $r) {
                if (!empty($r['skipped']))         $skipped++;
                elseif (!empty($r['ok']))          $created++;
                else                                $failed++;
            }
            echo json_encode([
                'ok'      => $failed === 0,
                'created' => $created,
                'skipped' => $skipped,
                'failed'  => $failed,
                'items'   => $res,
                'msg'     => "$created created · $skipped skipped · $failed failed",
            ]);
        } catch (Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /** AJAX: submit a DRAFT LPO into its approval chain. */
    public function submitAjAction()
    {
        $lid = (int)($_POST['lpo_id'] ?? 0);
        if ($lid <= 0) { $this->jsonResponse(['ok' => false, 'msg' => 'lpo_id required']); return; }

        $m = new Lpos();
        $l = $m->byId($lid);
        if (!$l) { $this->jsonResponse(['ok' => false, 'msg' => 'LPO not found']); return; }
        if ($l['cash_out_status'] !== 'DRAFT') {
            $this->jsonResponse(['ok' => false, 'msg' => 'LPO is not in DRAFT.']); return;
        }

        $chainKey = $l['approval_chain_key'] ?: (Lpos::TYPES()[$l['lpo_type']]['chain'] ?? '');
        if ($chainKey === '') {
            $this->jsonResponse(['ok' => false, 'msg' => 'No approval chain configured for this LPO type.']); return;
        }

        $ac  = new ApprovalChain();
        $ref = $l['lpo_no'] . ' · ' . ($l['payee_name'] ?: '(no payee)') . ' · ' . $l['currency'] . ' ' . number_format((float)$l['amount']);
        $res = $ac->submit([
            'chain_key'    => $chainKey,
            'entity_type'  => 'lpo',
            'entity_id'    => $lid,
            'ref_label'    => $ref,
            'return_url'   => 'Lpos/view/' . $lid,
            'user_id'      => $this->currentUserId(),
            'user_name'    => $_SESSION['full_name'] ?? '',
            'user_group'   => $_SESSION['user_group_name'] ?? '',
            'comment'      => 'LPO submitted for approval.',
        ]);
        if ($res['ok']) {
            $m->markSubmitted($lid, (int)$res['approval_id']);
        }
        $this->jsonResponse($res);
    }
}
