<?php
// ============================================================
//  MediCore — Customer Portal Shop API (browse / order / history)
//  File: api/customer-shop.php
//  Public endpoint gated by require_customer_login() per-action
//  (list_products is the only action a logged-out visitor can hit).
//  Accepts JSON POST, returns JSON.
//
//  IMPORTANT — this file must NEVER select or return cost/margin
//  fields (btp_vat, cost, wholesale_price, etc.) since the response
//  is visible to end customers. Only public-safe product columns
//  are selected in api_list_products().
// ============================================================
require_once '../includes/customer_auth.php';
session_write_close(); // release session lock — prevents request queuing

ensure_categories_schema();

header('Content-Type: application/json');

$body   = json_decode(file_get_contents('php://input'), true) ?? [];
$action = trim($body['action'] ?? '');

try {
    match ($action) {
        'list_products'  => api_list_products($body),
        'list_filters'   => api_list_filters($body),
        'place_order'    => api_place_order($body),
        'my_orders'      => api_my_orders($body),
        'order_detail'   => api_order_detail($body),
        default          => fail('Unknown action'),
    };
} catch (Throwable $e) {
    fail($e->getMessage());
}

// ── Browse products (public — no login required) ─────────────
// Search is broadened beyond just name/strength to also match the
// generic (active ingredient), company (manufacturer), form, and
// category name — customers often search by any of these. Category/
// form/stock-status filters can be ANDed on top, same shape as the
// staff-side api/products.php search this mirrors.
function api_list_products(array $b): void {
    if (!portal_enabled()) fail('Online ordering is currently unavailable.');

    $q     = trim($b['q'] ?? '');
    $where = ['p.is_active = 1', 'p.is_deleted = 0'];
    $params = [];
    if ($q) {
        $like = "%$q%";
        $where[] = "(p.name LIKE ? OR p.strength LIKE ? OR g.name LIKE ?
                      OR c.name LIKE ? OR p.form LIKE ? OR cat.name LIKE ?)";
        array_push($params, $like, $like, $like, $like, $like, $like);
    }

    $categoryId = (int) ($b['category_id'] ?? 0);
    if ($categoryId) { $where[] = "p.category_id = ?"; $params[] = $categoryId; }

    $form = trim($b['form'] ?? '');
    if ($form) { $where[] = "p.form = ?"; $params[] = $form; }

    $stockStatus = trim($b['stock_status'] ?? '');
    if ($stockStatus === 'in')  { $where[] = "p.stock_pcs > 0"; }
    if ($stockStatus === 'out') { $where[] = "p.stock_pcs <= 0"; }

    $wSql  = implode(' AND ', $where);
    $joins = "LEFT JOIN generics   g   ON g.id   = p.generic_id
              LEFT JOIN companies  c   ON c.id   = p.company_id
              LEFT JOIN categories cat ON cat.id = p.category_id";

    $page     = max(1, (int)($b['page'] ?? 1));
    $pageSize = max(10, min(100, (int)($b['page_size'] ?? 30)));
    $offset   = ($page - 1) * $pageSize;

    $total = (int) db_val("SELECT COUNT(*) FROM products p $joins WHERE $wSql", $params);

    // Public-safe columns ONLY — no btp_vat / cost / wholesale fields.
    $rows = db_rows(
        "SELECT p.id, p.name, p.form, p.strength, p.sale_price, p.stock_pcs,
                g.name AS generic_name, c.name AS company_name, cat.name AS category_name
           FROM products p
           $joins
          WHERE $wSql
       ORDER BY p.name
          LIMIT $pageSize OFFSET $offset",
        $params
    );
    foreach ($rows as &$r) {
        $stock = (int) $r['stock_pcs'];
        $r['in_stock'] = $stock > 0;
        unset($r['stock_pcs']); // exact count not exposed publicly
    }
    unset($r);

    ok([
        'data'        => $rows,
        'page'        => $page,
        'page_size'   => $pageSize,
        'total'       => $total,
        'total_pages' => (int) max(1, ceil($total / $pageSize)),
    ]);
}

// ── Filter option lists for the shop search bar (categories + forms) ──
// Public — no login required, same as list_products. Only returns
// categories/forms actually in use by active products, so the
// dropdown never shows an empty-result option.
function api_list_filters(array $b): void {
    if (!portal_enabled()) fail('Online ordering is currently unavailable.');

    $categories = db_rows(
        "SELECT DISTINCT cat.id, cat.name
           FROM categories cat
           JOIN products p ON p.category_id = cat.id
          WHERE p.is_active = 1 AND p.is_deleted = 0
       ORDER BY cat.name"
    );
    $forms = db_rows(
        "SELECT DISTINCT form FROM products
          WHERE is_active = 1 AND is_deleted = 0 AND form IS NOT NULL AND form != ''
       ORDER BY form"
    );
    ok(['categories' => $categories, 'forms' => array_column($forms, 'form')]);
}

// ── Place an order (pending — staff must approve; no stock/ledger
//    impact yet, same pattern as the PO Approval workflow) ────────
function api_place_order(array $b): void {
    require_customer_login();
    if (!portal_enabled()) fail('Online ordering is currently unavailable.');

    $items = $b['items'] ?? [];
    if (!$items) fail('Your cart is empty.');

    $address = trim($b['delivery_address'] ?? '');
    $notes   = trim($b['notes'] ?? '');

    $custId = current_customer_id();
    $customer = db_row("SELECT * FROM customers WHERE id = ?", [$custId]);
    if (!$customer) fail('Customer account not found.');

    // Price/cost are ALWAYS re-read from the database server-side —
    // a client-submitted price is never trusted (prevents tampering).
    // Stock is likewise re-checked server-side — the client-side
    // "Unavailable" button disable is UX only, not a security boundary.
    $allowBackorder = (get_settings()['portal_allow_backorder'] ?? '1') === '1';

    $lineItems = [];
    $subTotal  = 0.0;
    foreach ($items as $item) {
        $pid = (int) ($item['product_id'] ?? 0);
        $qty = max(1, (int) ($item['qty'] ?? 0));
        if (!$pid || $qty <= 0) continue;

        $p = db_row(
            "SELECT id, name, form, sale_price, btp_vat, stock_pcs
               FROM products WHERE id = ? AND is_active = 1 AND is_deleted = 0",
            [$pid]
        );
        if (!$p) continue;

        if (!$allowBackorder && (int) $p['stock_pcs'] <= 0) {
            fail('"' . $p['name'] . '" is currently out of stock and cannot be ordered.');
        }

        $price = (float) $p['sale_price'];
        $cost  = (float) $p['btp_vat'];
        $total = round($price * $qty, 2);
        $subTotal += $total;

        $lineItems[] = [
            'product_id'   => $pid,
            'product_name' => $p['name'],
            'form'         => $p['form'] ?? '',
            'qty'          => $qty,
            'price'        => $price,
            'cost'         => $cost,
            'total'        => $total,
        ];
    }
    if (!$lineItems) fail('No valid items in your cart.');

    $minOrder = portal_min_order();
    if ($minOrder > 0 && $subTotal < $minOrder) {
        fail('Minimum order amount is ' . number_format($minOrder, 2) . '.');
    }

    $orderNo = next_ref('ORD', 'online_orders', 'order_no');

    db()->beginTransaction();
    try {
        $orderId = db_insert(
            "INSERT INTO online_orders
               (order_no, customer_id, customer_name, customer_phone,
                delivery_address, sub_total, total, status, notes)
             VALUES (?,?,?,?,?,?,?,'pending',?)",
            [
                $orderNo, $custId, $customer['name'], $customer['phone'],
                $address, $subTotal, $subTotal, $notes,
            ]
        );

        foreach ($lineItems as $it) {
            db_run(
                "INSERT INTO online_order_items
                   (online_order_id, product_id, product_name, form, qty, price, cost, total)
                 VALUES (?,?,?,?,?,?,?,?)",
                [
                    $orderId, $it['product_id'], $it['product_name'], $it['form'],
                    $it['qty'], $it['price'], $it['cost'], $it['total'],
                ]
            );
        }

        db()->commit();

        // Confirmation to the customer — best-effort, never blocks the
        // order itself (send_* functions never throw).
        notify_order_placed($customer['phone'], $customer['email'] ?: null, $orderNo, $subTotal);

        ok([
            'order_id' => $orderId,
            'order_no' => $orderNo,
            'total'    => $subTotal,
            'msg'      => 'Order placed! Our team will review it shortly.',
        ]);
    } catch (Throwable $e) {
        db()->rollBack();
        fail('Could not place order: ' . $e->getMessage());
    }
}

// ── My order history ──────────────────────────────────────────
function api_my_orders(array $b): void {
    require_customer_login();
    $rows = db_rows(
        "SELECT oo.*,
                (SELECT COUNT(*) FROM online_order_items WHERE online_order_id = oo.id) AS item_count
           FROM online_orders oo
          WHERE oo.customer_id = ?
       ORDER BY oo.created_at DESC",
        [current_customer_id()]
    );
    ok(['data' => $rows]);
}

// ── One order's detail (ownership enforced) ──────────────────────
function api_order_detail(array $b): void {
    require_customer_login();
    $id = (int) ($b['id'] ?? 0);
    if (!$id) fail('Missing id');

    $order = db_row(
        "SELECT * FROM online_orders WHERE id = ? AND customer_id = ?",
        [$id, current_customer_id()]
    );
    if (!$order) fail('Order not found.');

    $items = db_rows(
        "SELECT * FROM online_order_items WHERE online_order_id = ? ORDER BY id",
        [$id]
    );
    ok(['data' => $order, 'items' => $items]);
}

// ── Response helpers ──────────────────────────────────────────
function ok(array $extra = []): void {
    echo json_encode(['success' => true] + $extra);
}
function fail(string $msg): never {
    echo json_encode(['success' => false, 'error' => $msg]);
    exit;
}
