<?php
class GoodsClientPosController extends Controller
{
    public function indexAction()
    {
        $m           = new GoodsClientPos();
        $status      = (string)($_GET['status'] ?? '');
        $projectId   = (int)($_GET['project_id'] ?? 0);
        $assignedMe  = strtolower((string)($_GET['assigned'] ?? '')) === 'me';
        $assignedUid = $assignedMe ? $this->currentUserId() : 0;
        $search      = trim((string)($_GET['q'] ?? ''));
        $perPage     = 25;
        $page        = max(1, (int)($_GET['page'] ?? 1));
        $offset      = ($page - 1) * $perPage;

        $total = $m->countList($status, $projectId, $assignedUid, $search);
        $pages = max(1, (int)ceil($total / $perPage));
        if ($page > $pages) { $page = $pages; $offset = ($page - 1) * $perPage; }
        $rows  = $m->list($status, $projectId, $assignedUid, $perPage, $offset, $search);

        // For the filter chip: resolve the project name once so the header can show it.
        $projName = '';
        if ($projectId > 0) {
            $db = new DB();
            $r = $db->mySelect("SELECT project_no, project_name FROM c_projects WHERE c0d = :id", [':id' => $projectId]);
            if (!empty($r)) $projName = $r[0]['project_no'] . ' · ' . $r[0]['project_name'];
        }

        // Slice 8Z-q · hide sell/margin/total for PC + Procurement roles (revenue not their business).
        $group = strtolower(trim((string)$this->currentUserGroup()));
        $hideRevenue = in_array($group, ['procurement','projects coordinator'], true);

        $this->view->setLayout($this->roleLayout());
        $this->view->render('GoodsClientPos/index', [
            'page_title'           => 'Client POs',
            'rows'                 => $rows,
            'status'               => $status,
            'filter_project'       => $projectId,
            'filter_project_label' => $projName,
            'filter_assigned_me'   => $assignedMe,
            'search'               => $search,
            'page'                 => $page,
            'pages'                => $pages,
            'per_page'             => $perPage,
            'total'                => $total,
            'hide_revenue'         => $hideRevenue,
        ]);
    }

    public function addAction()
    {
        $db = new DB();
        $projects = $db->mySelect(
            "SELECT c0d, project_no, project_name, project_type, project_customer,
                    project_customer_id, currency, default_margin_pct
               FROM c_projects
              WHERE deleted = 0 AND (crm_status = 'APPROVED' OR migrated_flag = 1)
              ORDER BY c0d DESC",
            []
        ) ?: [];
        $this->view->setLayout($this->roleLayout());
        $this->view->render('GoodsClientPos/add', [
            'page_title' => 'New Client PO',
            'projects'   => $projects,
            'fb_msg'     => '',
            'form'       => [],
        ]);
    }

    public function saveAction()
    {
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { Router::redirect('GoodsClientPos/add'); return; }

        $descs = $_POST['item_description']    ?? [];
        $qtys  = $_POST['item_qty']            ?? [];
        $units = $_POST['item_unit']           ?? [];
        $sells = $_POST['item_unit_price_sell']?? [];
        $cogs  = $_POST['item_unit_price_cog'] ?? [];
        $items = [];
        for ($i = 0; $i < count($descs); $i++) {
            if (trim((string)$descs[$i]) === '') continue;
            $items[] = [
                'description'     => (string)$descs[$i],
                'qty'             => (float)($qtys[$i]  ?? 0),
                'unit'            => (string)($units[$i] ?? ''),
                'unit_price_sell' => (float)($sells[$i] ?? 0),
                'unit_price_cog'  => (float)($cogs[$i]  ?? 0),
            ];
        }

        $d = [
            'project_id'         => (int)($_POST['project_id']  ?? 0),
            'customer_id'        => (int)($_POST['customer_id'] ?? 0),
            'source_module'      => (string)($_POST['source_module'] ?? 'goods'),
            'po_date'            => (string)($_POST['po_date'] ?? date('Y-m-d')),
            'delivery_by'        => (string)($_POST['delivery_by'] ?? ''),
            'currency'           => (string)($_POST['currency'] ?? 'TZS'),
            'vat_rate'           => (float) ($_POST['vat_rate'] ?? 18),
            'default_margin_pct' => (float) ($_POST['default_margin_pct'] ?? 25),
            'customer_po_ref'    => (string)($_POST['customer_po_ref'] ?? ''),
            'payment_terms'      => (string)($_POST['payment_terms'] ?? ''),
            'delivery_poc'       => (string)($_POST['delivery_poc'] ?? ''),
            'delivery_poc_phone' => (string)($_POST['delivery_poc_phone'] ?? ''),
            'delivery_address'   => (string)($_POST['delivery_address'] ?? ''),
            'notes'              => (string)($_POST['notes'] ?? ''),
            'items'              => $items,
        ];

        $m = new GoodsClientPos();
        $r = $m->create($d, $this->currentUserId(), $_SESSION['full_name'] ?? '');
        if (!$r['ok']) {
            $db = new DB();
            $projects = $db->mySelect(
                "SELECT c0d, project_no, project_name, project_type, project_customer, project_customer_id, currency, default_margin_pct
                   FROM c_projects WHERE deleted = 0 AND (crm_status = 'APPROVED' OR migrated_flag = 1)
                  ORDER BY c0d DESC", []
            ) ?: [];
            $this->view->setLayout($this->roleLayout());
            $this->view->render('GoodsClientPos/add', [
                'page_title' => 'New Client PO',
                'projects'   => $projects,
                'fb_msg'     => '<div class="alert alert-danger">' . htmlspecialchars($r['msg']) . '</div>',
                'form'       => $_POST,
            ]);
            return;
        }
        Router::redirect('GoodsClientPos/view/' . (int)$r['id']);
    }

    /**
     * Project-scoped alias — from the ProjectStarter checklist we know a project_id, not a PO id.
     * If exactly one client PO belongs to the project, redirect to its detail view.
     * If more than one, land on a filtered index. If none, land on the add screen.
     */
    public function forProjectAction($projectId = 0)
    {
        $projectId = (int)$projectId;
        $db = new DB();
        $rs = $db->mySelect(
            "SELECT c0d FROM goods_client_pos WHERE project_id = :p AND deleted = 0 ORDER BY c0d DESC",
            [':p' => $projectId]
        ) ?: [];
        if (count($rs) === 1) { Router::redirect('GoodsClientPos/view/' . (int)$rs[0]['c0d']); return; }
        if (empty($rs))       { Router::redirect('GoodsClientPos/add?project_id=' . $projectId); return; }
        // Multiple: fall through to index filtered by project
        Router::redirect('GoodsClientPos?project_id=' . $projectId);
    }

    public function viewAction($id = 0)
    {
        $m  = new GoodsClientPos();
        $po = $m->byId((int)$id);
        if (!$po) {
            $this->view->setLayout($this->roleLayout());
            $this->view->render('generals/fb_page', ['fb_msg' => '<div class="alert alert-danger">Client PO not found.</div>']);
            return;
        }
        $items    = $m->itemsFor((int)$id);
        $invoices = $m->invoicesFor((int)$id);

        $group     = strtolower($this->currentUserGroup());
        $canInvoice = in_array($group, ['projects coordinator','procurement','accountant','administrator','executive'], true)
                      && in_array($po['status'], ['DRAFT','CONFIRMED','INVOICING','SOURCING'], true);

        // DN load + can-deliver check
        $dnm = new GoodsCustomerDelivery();
        $dns = $dnm->forClientPo((int)$id);
        // Any line with in-warehouse stock (qty_received - qty_delivered > 0) and status not CLOSED/CANCELLED
        $canDeliver = in_array($group, ['procurement','stores','administrator'], true)
                      && !in_array($po['status'], ['CLOSED','CANCELLED'], true);
        if ($canDeliver) {
            $anyDeliverable = false;
            foreach ($items as $it) {
                $inWh = (float)$it['qty_received'] - (float)$it['qty_delivered'];
                $outstd = (float)$it['qty']         - (float)$it['qty_delivered'];
                if (min($inWh, $outstd) > 0.0001) { $anyDeliverable = true; break; }
            }
            $canDeliver = $anyDeliverable;
        }

        // Slice 8Z-g Batch 1 · Procurement scrub surface
        $isPC       = ($group === 'projects coordinator');
        $isProc     = ($group === 'procurement');
        // Feedback rule: PC/Procurement see cost side only — hide Sell, Margin, Total-incl-VAT.
        // They see COG (their ceiling) + overheads. Revenue-side belongs to PM/FM/Exec/Accountant.
        $hideRevenue = ($isPC || $isProc);
        $hideMargin  = $hideRevenue;
        $hideSell    = $hideRevenue;
        $canAccept  = in_array($group, ['procurement','administrator'], true) && $po['status'] !== 'CANCELLED';
        $canQuery   = in_array($group, ['procurement','administrator'], true) && $po['status'] !== 'CANCELLED';
        $canReopen  = in_array($group, ['projects coordinator','administrator'], true) && $po['status'] !== 'CANCELLED';
        $overheads  = $m->overheadsForPo((int)$id, (int)$po['project_id']);
        // Slice 8Z-h · Batch 2 — sourcing path picker data
        $canPickPath = in_array($group, ['procurement','administrator'], true) && !in_array($po['status'], ['CLOSED','CANCELLED'], true);
        $engagement  = $m->discoveryEngagementForPo((int)$id);
        // Slice 8Z-q · Related LPOs (supplier LPOs + internal-cash draws sourced from this PO)
        $relatedLpos = (new Lpos())->forClientPo((int)$id);

        $this->view->setLayout($this->roleLayout());
        $this->view->render('GoodsClientPos/view', [
            'page_title'  => $po['client_po_no'] . ' · ' . $po['customer_name'],
            'po'          => $po,
            'items'       => $items,
            'invoices'    => $invoices,
            'can_invoice' => $canInvoice,
            'dns'         => $dns,
            'can_deliver' => $canDeliver,
            'hide_margin'   => $hideMargin,
            'hide_sell'     => $hideSell,
            'can_accept'    => $canAccept,
            'can_pick_path' => $canPickPath,
            'engagement'    => $engagement,
            // Batch 4 · Discovery cap+threshold are % of PO total, admin-tunable
            'discovery_cap_pct'       => (float) (new FinanceSettings())->getFloat('discovery_cap_pct', 5),
            'discovery_exec_pct'      => (float) (new FinanceSettings())->getFloat('discovery_exec_threshold_pct', 3),
            'can_query'   => $canQuery,
            'can_reopen'  => $canReopen,
            'overheads'   => $overheads,
            'related_lpos' => $relatedLpos,
        ]);
    }

    /* ── Slice 8Z-g · Procurement scrub endpoints ────────────────────── */

    public function acceptItemAjAction()
    {
        header('Content-Type: application/json');
        try {
            $poId   = (int)($_POST['po_id']   ?? 0);
            $itemId = (int)($_POST['item_id'] ?? 0);
            $note   = (string)($_POST['note'] ?? '');
            if ($poId <= 0 || $itemId <= 0) throw new InvalidArgumentException('po_id + item_id required.');
            $r = (new GoodsClientPos())->acceptItem($poId, $itemId, $this->currentUserId(), $_SESSION['full_name'] ?? '', $note);
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function queryItemAjAction()
    {
        header('Content-Type: application/json');
        try {
            $poId   = (int)($_POST['po_id']   ?? 0);
            $itemId = (int)($_POST['item_id'] ?? 0);
            $note   = (string)($_POST['note'] ?? '');
            if ($poId <= 0 || $itemId <= 0) throw new InvalidArgumentException('po_id + item_id required.');
            $r = (new GoodsClientPos())->queryItem($poId, $itemId, $this->currentUserId(), $_SESSION['full_name'] ?? '', $note);
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    public function reopenItemAjAction()
    {
        header('Content-Type: application/json');
        try {
            $poId   = (int)($_POST['po_id']   ?? 0);
            $itemId = (int)($_POST['item_id'] ?? 0);
            $note   = (string)($_POST['note'] ?? '');
            if ($poId <= 0 || $itemId <= 0) throw new InvalidArgumentException('po_id + item_id required.');
            $r = (new GoodsClientPos())->reopenItem($poId, $itemId, $this->currentUserId(), $_SESSION['full_name'] ?? '', $note);
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /* ── Slice 8Z-h · Batch 2 · Sourcing path picker ────────────────── */

    /** Lock in the sourcing route (DISCOVERY or SKIP_TO_RFQ). One-way — cannot be flipped. */
    public function setSourcingPathAjAction()
    {
        header('Content-Type: application/json');
        try {
            $poId = (int)($_POST['po_id'] ?? 0);
            $path = (string)($_POST['path'] ?? '');
            if ($poId <= 0) throw new InvalidArgumentException('po_id required.');
            $r = (new GoodsClientPos())->setSourcingPath($poId, $path, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            echo json_encode($r);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /**
     * Path A · Product Discovery request.
     * Creates a PRODUCT_DISCOVERY LPO (via existing Lpos::create), threads it to this PO via
     * sourced_from_client_po_id, submits the approval chain (200K threshold routes to Exec),
     * fires the standard cash-request notification to PM.
     *
     * POST: po_id, amount, purpose_category (Market research / Sample purchase / Travel / Trade fair / Other), notes
     */
    public function requestDiscoveryAjAction()
    {
        header('Content-Type: application/json');
        try {
            $poId    = (int)($_POST['po_id'] ?? 0);
            $amount  = (float)($_POST['amount'] ?? 0);
            // Slice 8Z-j Batch 4 — multi-select purposes (comma-joined). Fallback to single legacy field.
            $purposesRaw = $_POST['purpose_categories'] ?? $_POST['purpose_category'] ?? '';
            $purposes = is_array($purposesRaw) ? $purposesRaw : array_filter(array_map('trim', explode(',', (string)$purposesRaw)));
            $notes   = trim((string)($_POST['notes'] ?? ''));
            if ($poId <= 0)         throw new InvalidArgumentException('po_id required.');
            if ($amount <= 0)       throw new InvalidArgumentException('Amount must be > 0.');
            $allowedPurposes = ['Market research','Sample purchase','Travel to supplier','Trade fair','Other'];
            $purposes = array_values(array_intersect($purposes, $allowedPurposes));
            if (empty($purposes)) throw new InvalidArgumentException('Pick at least one purpose category.');
            $purpLabel = implode(', ', $purposes);

            // Load PO for currency + project_id + client_po_no + total (used for % caps)
            $m  = new GoodsClientPos();
            $po = $m->byId($poId);
            if (!$po)                                             throw new RuntimeException('Client PO not found.');
            if ($po['sourcing_path'] !== 'DISCOVERY')             throw new RuntimeException('Sourcing path is not DISCOVERY (' . $po['sourcing_path'] . ').');
            // Idempotency: refuse if a Discovery LPO already exists on this PO.
            $existing = $m->discoveryEngagementForPo($poId);
            if (!empty($existing))                                throw new RuntimeException('A Discovery LPO already exists on this PO (' . $existing['lpo_no'] . ').');

            // Slice 8Z-j Batch 4 — cap enforcement (% of PO total, admin-tunable).
            if (!class_exists('FinanceSettings')) require_once ROOT . '/app/models/FinanceSettings.php';
            $fs      = new FinanceSettings();
            $capPct  = $fs->getFloat('discovery_cap_pct', 5);
            $poTotal = (float)$po['total'];
            if ($poTotal > 0) {
                $capAmount = $poTotal * ($capPct / 100.0);
                if ($amount > $capAmount) {
                    throw new RuntimeException(
                        'Amount exceeds the Discovery cap (' . $capPct . '% of PO total = ' .
                        $po['currency'] . ' ' . number_format($capAmount) . '). ' .
                        'Reduce the amount or ask FM/Executive to raise the cap in Finance Settings.'
                    );
                }
            }

            // Purpose string wraps the categories so it's visible on the LPO detail + approval strip.
            $purposeText = 'Product Discovery · ' . $purpLabel . ' · PO ' . $po['client_po_no']
                         . ($notes ? ' — ' . $notes : '');

            $lm  = new Lpos();
            $cr  = $lm->create([
                'lpo_type'                => 'PRODUCT_DISCOVERY',
                'source_module'           => 'goods',
                'project_id'              => (int)$po['project_id'],
                'payee_type'              => 'employee',
                'payee_id'                => $this->currentUserId(),
                'payee_name'              => $_SESSION['full_name'] ?? '',
                'amount'                  => $amount,
                'currency'                => (string)$po['currency'],
                'purpose'                 => $purposeText,
                'sourced_from_client_po_id' => $poId,
                'supplier_receipt_type'   => 'NORMAL',
            ], $this->currentUserId(), $_SESSION['full_name'] ?? '');
            if (empty($cr['ok'])) { echo json_encode($cr); return; }
            $lpoId  = (int)$cr['id'];
            $lpoRow = $lm->byId($lpoId);

            // Slice 8Z-k · capture Procurement's preferred payment method + account (optional).
            $prefMethod = (string)($_POST['preferred_method'] ?? '');
            $prefAcct   = (int)   ($_POST['preferred_account_id'] ?? 0);
            if ($prefMethod !== '' || $prefAcct > 0) {
                $lm->setRequesterPayment($lpoId, $prefMethod !== '' ? $prefMethod : null, $prefAcct ?: null);
            }

            // Submit into the correct chain — Discovery routes based on % of PO total (admin-tunable).
            $chainKey = Lpos::chainForAmount('PRODUCT_DISCOVERY', $amount, $poTotal);
            if (!class_exists('ApprovalChain')) require_once ROOT . '/app/models/ApprovalChain.php';
            $ac  = new ApprovalChain();
            $ref = $lpoRow['lpo_no'] . ' · Product Discovery · ' . $po['currency'] . ' ' . number_format($amount);
            $sres = $ac->submit([
                'chain_key'    => $chainKey,
                'entity_type'  => 'lpo',
                'entity_id'    => $lpoId,
                'ref_label'    => $ref,
                'return_url'   => 'Lpos/view/' . $lpoId,
                'user_id'      => $this->currentUserId(),
                'user_name'    => $_SESSION['full_name']       ?? '',
                'user_group'   => $_SESSION['user_group_name'] ?? '',
                'comment'      => 'Product Discovery request for ' . $po['client_po_no'] . ' (' . $purpCat . ').',
            ]);
            if (empty($sres['ok'])) { echo json_encode(['ok' => false, 'msg' => 'Draft saved but submit failed: ' . ($sres['msg'] ?? '')]); return; }
            $lm->markSubmitted($lpoId, (int)$sres['approval_id']);

            // Fan-out notification to PM (same pattern as CashRequests::saveAj)
            try {
                if (!class_exists('Notifications')) require_once ROOT . '/app/models/Notifications.php';
                $subject = 'Product Discovery request awaiting your check: ' . $lpoRow['lpo_no'];
                $body    = $po['currency'] . ' ' . number_format($amount) . ' · ' . $purpCat . ' for PO ' . $po['client_po_no'] . ($notes ? ' — ' . $notes : '');
                $url     = 'Lpos/view/' . $lpoId;
                $db = new DB();
                $pmRow = $db->mySelect("SELECT project_manager_id FROM c_projects WHERE c0d = :p", [':p' => (int)$po['project_id']]);
                $pmId  = (int)($pmRow[0]['project_manager_id'] ?? 0);
                if ($pmId > 0) (new Notifications())->notify($pmId, 'DISCOVERY_PENDING_PM', $subject, $body, $url, 'lpo', $lpoId, $this->currentUserId(), $_SESSION['full_name'] ?? '');
                else           (new Notifications())->notifyGroup('Project Manager', 'DISCOVERY_PENDING_PM', $subject, $body, $url, 'lpo', $lpoId, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            } catch (\Throwable $e) { /* best-effort */ }

            echo json_encode(['ok' => true, 'lpo_id' => $lpoId, 'lpo_no' => $lpoRow['lpo_no'], 'chain_key' => $chainKey, 'msg' => 'Discovery submitted for ' . $chainKey . '.']);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }
}
