<?php
/**
 * GET /api/products/list.php
 * Query params:
 *   scope=marketplace (default) | mine | admin
 *   search=<text>            match on product name
 *   category_id=<int>
 *   max_price=<number>
 *   location=<text>          partial match
 *   country=<text>, city=<text>   exact match, from products/locations.php dropdowns
 *   near_lat=<float>, near_lng=<float>, max_distance_km=<float>
 *                            filter/sort by distance from a point (seller's saved
 *                            pin location) - all three must be present together
 *   status=<active|inactive|sold|expired>  (admin scope only)
 *   sort=newest|expiry|price_asc|price_desc|distance   (default newest)
 *   page=<int> (default 1)   per_page fixed at 20
 *
 * scope=mine requires login and returns ALL of the caller's own listings
 * regardless of status, so "My Listings" can manage everything.
 * scope=admin requires the admin role and returns EVERY listing from every
 * seller, for moderation.
 * scope=marketplace is public and only shows active, non-expired listings.
 */

require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../includes/response.php';
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/settings.php';

$scopeParam = $_GET['scope'] ?? 'marketplace';
$scope = in_array($scopeParam, ['mine', 'admin'], true) ? $scopeParam : 'marketplace';
$page = max(1, (int) ($_GET['page'] ?? 1));
$perPage = 20;
$offset = ($page - 1) * $perPage;

$pdo = Database::connect();

$where = [];
$params = [];
$sellerJoin = '';

if ($scope === 'mine') {
    $userId = require_login();
    $where[] = 'p.owner_id = ?';
    $params[] = $userId;
} elseif ($scope === 'admin') {
    require_role(['admin']);
    $sellerJoin = 'JOIN users su ON su.id = p.owner_id';
    if (!empty($_GET['status'])) {
        $where[] = 'p.status = ?';
        $params[] = $_GET['status'];
    }
} else {
    $where[] = "p.status = 'active'";
    $where[] = '(p.expiry_date IS NULL OR p.expiry_date >= CURDATE())';

    // Admin-configured market scope (Settings tab) restricts the whole public
    // marketplace to one country/city, e.g. for a phased regional launch.
    // Listings with NO country/city set are always included rather than
    // silently hidden - a product created before geo fields existed (or
    // simply left blank) isn't "wrong region", it's "unknown region", and
    // treating unknown as excluded means turning this setting on can make
    // the entire marketplace look empty with no obvious explanation.
    $marketCountry = get_setting($pdo, 'market_country', '');
    $marketCity = get_setting($pdo, 'market_city', '');
    if ($marketCountry !== '') {
        $where[] = "(p.country = ? OR p.country IS NULL OR p.country = '')";
        $params[] = $marketCountry;
    }
    if ($marketCity !== '') {
        $where[] = "(p.city = ? OR p.city IS NULL OR p.city = '')";
        $params[] = $marketCity;
    }
}

// Buyer-chosen geo filter (on top of any market-wide restriction above).
if (!empty($_GET['country'])) {
    $where[] = 'p.country = ?';
    $params[] = $_GET['country'];
}
if (!empty($_GET['city'])) {
    $where[] = 'p.city = ?';
    $params[] = $_GET['city'];
}
if (!empty($_GET['vendor_id'])) {
    $where[] = 'p.owner_id = ?';
    $params[] = (int) $_GET['vendor_id'];
}

if (!empty($_GET['search'])) {
    $where[] = 'p.name LIKE ?';
    $params[] = '%' . $_GET['search'] . '%';
}

if (!empty($_GET['category_id'])) {
    $where[] = 'p.category_id = ?';
    $params[] = (int) $_GET['category_id'];
}

// Admin-only convenience filter: review what's currently boosted, or
// specifically find listings that AREN'T, without paging through everything.
if (isset($_GET['featured']) && $_GET['featured'] !== '') {
    if ($_GET['featured'] === '1') {
        $where[] = "(p.is_featured = 1 AND (p.featured_until IS NULL OR p.featured_until >= CURDATE()))";
    } elseif ($_GET['featured'] === '0') {
        $where[] = "NOT (p.is_featured = 1 AND (p.featured_until IS NULL OR p.featured_until >= CURDATE()))";
    }
}

if (!empty($_GET['max_price'])) {
    $where[] = 'p.selling_price <= ?';
    $params[] = (float) $_GET['max_price'];
}

if (!empty($_GET['location'])) {
    $where[] = 'p.location LIKE ?';
    $params[] = '%' . $_GET['location'] . '%';
}

// --- Distance filtering (from the seller's saved pin location) ---
// Unlike country/city, a listing whose seller has no pin set genuinely
// CAN'T be measured, so it's correctly excluded rather than "included by
// default" - there's no sensible default distance for an unknown location.
$hasDistanceFilter = isset($_GET['near_lat'], $_GET['near_lng'])
    && is_numeric($_GET['near_lat']) && is_numeric($_GET['near_lng']);
$distanceSelect = '';
$distanceJoin = '';
$havingSql = '';
$havingParams = [];

if ($hasDistanceFilter) {
    $lat = (float) $_GET['near_lat'];
    $lng = (float) $_GET['near_lng'];

    $distanceJoin = '
        LEFT JOIN business_profiles geo_bp ON geo_bp.user_id = p.owner_id
        LEFT JOIN personal_profiles geo_pp ON geo_pp.user_id = p.owner_id
    ';
    $distanceSelect = ",
        (6371 * ACOS(LEAST(1, GREATEST(-1,
            COS(RADIANS(?)) * COS(RADIANS(COALESCE(geo_bp.latitude, geo_pp.latitude)))
            * COS(RADIANS(COALESCE(geo_bp.longitude, geo_pp.longitude)) - RADIANS(?))
            + SIN(RADIANS(?)) * SIN(RADIANS(COALESCE(geo_bp.latitude, geo_pp.latitude)))
        )))) AS distance_km
    ";
    // Three placeholders for the Haversine formula itself, used in every SELECT.
    $distanceSelectParams = [$lat, $lng, $lat];

    if (!empty($_GET['max_distance_km']) && is_numeric($_GET['max_distance_km'])) {
        $havingSql = 'HAVING distance_km IS NOT NULL AND distance_km <= ?';
        $havingParams[] = (float) $_GET['max_distance_km'];
    } else {
        // No radius given, but coordinates were - still only show listings
        // that actually have a measurable distance (i.e. seller has a pin).
        $havingSql = 'HAVING distance_km IS NOT NULL';
    }
}

$orderMap = [
    'newest'     => 'p.created_at DESC',
    'expiry'     => 'p.expiry_date ASC',
    'price_asc'  => 'p.selling_price ASC',
    'price_desc' => 'p.selling_price DESC',
];
if ($hasDistanceFilter) {
    $orderMap['distance'] = 'distance_km ASC';
}
$requestedSort = $_GET['sort'] ?? 'newest';
if ($requestedSort === 'distance' && !$hasDistanceFilter) {
    $requestedSort = 'newest'; // distance sort makes no sense without a reference point
}
$sort = $orderMap[$requestedSort] ?? $orderMap['newest'];

// Featured listings sort to the top of the public marketplace only - "mine"
// and "admin" are management views where the seller's/admin's chosen sort
// should win outright, not be reshuffled by a promotional flag.
$featuredSortSql = "(p.is_featured = 1 AND (p.featured_until IS NULL OR p.featured_until >= CURDATE())) DESC, ";
if ($scope !== 'marketplace') {
    $featuredSortSql = '';
}
$sort = $featuredSortSql . $sort;

$whereSql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';

// Total count for pagination - wrapped in a subquery so it respects the
// HAVING clause on the computed distance column too.
$countSql = "SELECT COUNT(*) FROM (
    SELECT p.id" . $distanceSelect . "
    FROM products p
    $distanceJoin
    $whereSql
    $havingSql
) counted";
$countStmt = $pdo->prepare($countSql);
$countStmt->execute($hasDistanceFilter ? array_merge($params, $distanceSelectParams, $havingParams) : $params);
$total = (int) $countStmt->fetchColumn();

$sql = "
    SELECT p.id, p.name, p.description, p.quantity, p.original_price, p.selling_price,
           p.expiry_date, p.location, p.status, p.barcode, p.created_at,
           p.is_featured, p.featured_until,
           (p.is_featured = 1 AND (p.featured_until IS NULL OR p.featured_until >= CURDATE())) AS is_currently_featured,
           c.name AS category_name,
           " . ($scope === 'admin' ? 'p.owner_id, su.email AS owner_email, su.account_type AS owner_account_type,' : '') . "
           (SELECT image_path FROM product_images pi WHERE pi.product_id = p.id ORDER BY sort_order ASC LIMIT 1) AS thumbnail,
           (SELECT AVG(rating) FROM product_reviews pr WHERE pr.product_id = p.id) AS avg_rating,
           (SELECT COUNT(*) FROM product_reviews pr WHERE pr.product_id = p.id) AS review_count
           $distanceSelect
    FROM products p
    LEFT JOIN categories c ON c.id = p.category_id
    $sellerJoin
    $distanceJoin
    $whereSql
    $havingSql
    ORDER BY $sort
    LIMIT $perPage OFFSET $offset
";
$stmt = $pdo->prepare($sql);
$stmt->execute($hasDistanceFilter ? array_merge($params, $distanceSelectParams, $havingParams) : $params);
$products = $stmt->fetchAll();

foreach ($products as &$p) {
    $p['thumbnail_url'] = $p['thumbnail'] ? UPLOAD_URL . $p['thumbnail'] : null;
    unset($p['thumbnail']);
    $p['avg_rating'] = $p['avg_rating'] ? round((float) $p['avg_rating'], 1) : 0;
    $p['review_count'] = (int) $p['review_count'];
    $p['is_featured'] = (bool) $p['is_featured'];
    $p['is_currently_featured'] = (bool) $p['is_currently_featured'];
    if (array_key_exists('distance_km', $p)) {
        $p['distance_km'] = $p['distance_km'] !== null ? round((float) $p['distance_km'], 1) : null;
    }
}

send_json(true, 'OK', [
    'products' => $products,
    'pagination' => [
        'page' => $page,
        'per_page' => $perPage,
        'total' => $total,
        'total_pages' => (int) ceil($total / $perPage),
    ],
]);
