<?php
/**
 * Baskit catalogue API — thin read-only JSON gateway to Dokploy MySQL.
 *
 * Routes (relative to this folder):
 *   GET ?r=aisles
 *   GET ?r=products&bucket=&q=&page=
 *   GET ?r=product&barcode=
 *   GET ?r=deals&limit=
 *   GET ?r=stats
 */

declare(strict_types=1);

require __DIR__ . '/config.php';

header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: Content-Type, X-Api-Key');
header('Access-Control-Allow-Methods: GET, OPTIONS');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

$env = baskit_env();

// --- API key check ---
$given = $_SERVER['HTTP_X_API_KEY'] ?? ($_GET['key'] ?? '');
if (!hash_equals((string) $env['api_key'], (string) $given)) {
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}

// --- Simple per-IP rate limit (file-based) ---
rate_limit((string) ($_SERVER['REMOTE_ADDR'] ?? '0'), (int) ($env['rate_limit'] ?? 120));

$route = (string) ($_GET['r'] ?? '');

try {
    $db = baskit_db();
    switch ($route) {
        case 'aisles':
            echo json_encode(list_aisles($db));
            break;
        case 'products':
            echo json_encode(list_products($db));
            break;
        case 'product':
            echo json_encode(get_product($db));
            break;
        case 'deals':
            echo json_encode(list_deals($db));
            break;
        case 'stats':
            echo json_encode(get_stats($db));
            break;
        default:
            http_response_code(404);
            echo json_encode(['error' => 'Unknown route', 'hint' => 'Use ?r=aisles|products|product|deals|stats']);
    }
} catch (Throwable $ex) {
    http_response_code(500);
    echo json_encode(['error' => 'Server error', 'detail' => $ex->getMessage()]);
}

// ---------------------------------------------------------------------------

function rate_limit(string $ip, int $perMinute): void
{
    $dir = sys_get_temp_dir() . '/baskit_rl';
    if (!is_dir($dir)) {
        @mkdir($dir, 0700, true);
    }
    $file = $dir . '/' . preg_replace('/[^a-zA-Z0-9._-]/', '_', $ip) . '.json';
    $now  = time();
    $window = $now - 60;
    $hits = [];
    if (is_file($file)) {
        $raw = @file_get_contents($file);
        $hits = $raw ? (json_decode($raw, true) ?: []) : [];
        $hits = array_values(array_filter($hits, static fn ($t) => (int) $t >= $window));
    }
    if (count($hits) >= $perMinute) {
        http_response_code(429);
        echo json_encode(['error' => 'Rate limit exceeded']);
        exit;
    }
    $hits[] = $now;
    @file_put_contents($file, json_encode($hits), LOCK_EX);
}

/** Canonical shop categories — all products map into one of these + Other. */
function canonical_catalog(): array
{
    return [
        'vas_airtime'     => ['label' => 'VAS / Airtime',       'icon' => 'airtime'],
        'home_essentials' => ['label' => 'Home Essentials',     'icon' => 'home'],
        'fresh_produce'   => ['label' => 'Fresh Fruits & Veg',  'icon' => 'fresh'],
        'bakery'          => ['label' => 'Bakery',              'icon' => 'bakery'],
        'dairy_eggs'      => ['label' => 'Dairy & Eggs',        'icon' => 'dairy'],
        'meat_poultry'    => ['label' => 'Meat & Poultry',      'icon' => 'meat'],
        'other'           => ['label' => 'Other',               'icon' => 'other'],
    ];
}

/** Map pipeline / legacy bucket text to a canonical category slug. */
function normalize_bucket(?string $raw): string
{
    $key = strtolower(trim((string) $raw));
    if ($key === '') {
        return 'other';
    }

    static $exact = [
        'fresh_produce'        => 'fresh_produce',
        'dairy_bakery_chilled' => 'dairy_eggs',
        'household_cleaning'   => 'home_essentials',
        'staples'              => 'other',
        'pantry'               => 'other',
        'premium'              => 'other',
        'basket_builders'      => 'other',
        'vas_airtime'          => 'vas_airtime',
        'home_essentials'      => 'home_essentials',
        'bakery'               => 'bakery',
        'dairy_eggs'           => 'dairy_eggs',
        'meat_poultry'         => 'meat_poultry',
        'other'                => 'other',
    ];
    if (isset($exact[$key])) {
        return $exact[$key];
    }

    $rules = [
        'vas_airtime'     => ['airtime', 'vas', 'cellphone', 'mobile', 'prepaid', 'data bundle'],
        'home_essentials' => ['home', 'household', 'cleaning', 'detergent', 'laundry'],
        'fresh_produce'   => ['fresh', 'fruit', 'veg', 'produce', 'salad'],
        'bakery'          => ['bakery', 'bread', 'roll', 'pastry', 'cake', 'muffin'],
        'dairy_eggs'      => ['dairy', 'milk', 'cheese', 'egg', 'yoghurt', 'yogurt', 'butter', 'cream'],
        'meat_poultry'    => ['meat', 'poultry', 'chicken', 'beef', 'pork', 'lamb', 'fish', 'seafood', 'wors', 'bacon'],
    ];
    foreach ($rules as $canon => $needles) {
        foreach ($needles as $needle) {
            if (str_contains($key, $needle)) {
                return $canon;
            }
        }
    }

    return 'other';
}

/** SQL expression: raw mvp_catalogue.bucket → canonical slug. */
function bucket_sql_case(string $column = 'bucket'): string
{
    $b = $column;

    return "CASE
        WHEN $b IS NULL OR TRIM($b) = '' THEN 'other'
        WHEN $b IN ('fresh_produce') THEN 'fresh_produce'
        WHEN $b IN ('dairy_bakery_chilled') THEN 'dairy_eggs'
        WHEN $b IN ('household_cleaning') THEN 'home_essentials'
        WHEN $b IN ('staples','pantry','premium','basket_builders') THEN 'other'
        WHEN LOWER($b) LIKE '%airtime%' OR LOWER($b) LIKE '%vas%' THEN 'vas_airtime'
        WHEN LOWER($b) LIKE '%bakery%' OR LOWER($b) LIKE '%bread%' THEN 'bakery'
        WHEN LOWER($b) LIKE '%fresh%' OR LOWER($b) LIKE '%fruit%' OR LOWER($b) LIKE '%veg%' THEN 'fresh_produce'
        WHEN LOWER($b) LIKE '%dairy%' OR LOWER($b) LIKE '%milk%' OR LOWER($b) LIKE '%egg%' THEN 'dairy_eggs'
        WHEN LOWER($b) LIKE '%meat%' OR LOWER($b) LIKE '%poultry%' OR LOWER($b) LIKE '%chicken%' OR LOWER($b) LIKE '%fish%' THEN 'meat_poultry'
        WHEN LOWER($b) LIKE '%home%' OR LOWER($b) LIKE '%household%' OR LOWER($b) LIKE '%clean%' THEN 'home_essentials'
        ELSE 'other'
    END";
}

/** @deprecated use canonical_catalog() + normalize_bucket() */
function aisle_meta(?string $bucket): array
{
    $canon = normalize_bucket($bucket);
    $cat   = canonical_catalog()[$canon];

    return [
        'bucket' => $canon,
        'label'  => $cat['label'],
        'icon'   => $cat['icon'],
    ];
}

function list_aisles(PDO $db): array
{
    $case = bucket_sql_case('bucket');
    $sql  = "SELECT canon AS bucket, COUNT(*) AS product_count
             FROM (SELECT $case AS canon FROM mvp_catalogue) grouped
             GROUP BY canon";
    $counts = [];
    foreach ($db->query($sql)->fetchAll() as $row) {
        $counts[$row['bucket']] = (int) $row['product_count'];
    }

    $aisles = [];
    foreach (canonical_catalog() as $bucket => $meta) {
        $aisles[] = [
            'bucket'        => $bucket,
            'label'         => $meta['label'],
            'icon'          => $meta['icon'],
            'product_count' => $counts[$bucket] ?? 0,
        ];
    }

    return ['aisles' => $aisles];
}

function list_products(PDO $db): array
{
    $bucket = isset($_GET['bucket']) ? trim((string) $_GET['bucket']) : '';
    $q      = isset($_GET['q']) ? trim((string) $_GET['q']) : '';
    $page   = max(1, (int) ($_GET['page'] ?? 1));
    $limit  = min(50, max(1, (int) ($_GET['limit'] ?? 24)));
    $offset = ($page - 1) * $limit;

    $where  = [];
    $params = [];
    if ($bucket !== '') {
        $canon = normalize_bucket($bucket);
        $case  = bucket_sql_case('m.bucket');
        $where[] = "$case = ?";
        $params[] = $canon;
    }
    if ($q !== '') {
        $where[] = '(name LIKE ? OR brand LIKE ?)';
        $like = '%' . $q . '%';
        $params[] = $like;
        $params[] = $like;
    }
    $whereSql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';

    $countStmt = $db->prepare("SELECT COUNT(*) FROM mvp_catalogue m $whereSql");
    $countStmt->execute($params);
    $total = (int) $countStmt->fetchColumn();

    $sql = "SELECT m.barcode, m.bucket, m.name, m.brand, m.n_retailers,
                   m.pnp_price, m.checkers_price, m.woolworths_price,
                   m.min_price, m.max_price,
                   (SELECT c.image_url FROM catalogue c
                    WHERE c.barcode_norm = m.barcode AND c.image_url IS NOT NULL
                    AND c.image_url <> '' LIMIT 1) AS image_url
            FROM mvp_catalogue m
            $whereSql
            ORDER BY (m.max_price - m.min_price) DESC, m.name ASC
            LIMIT $limit OFFSET $offset";
    $stmt = $db->prepare($sql);
    $stmt->execute($params);
    $products = array_map('map_product', $stmt->fetchAll());

    return [
        'page'     => $page,
        'limit'    => $limit,
        'total'    => $total,
        'products' => $products,
    ];
}

function get_product(PDO $db): array
{
    $barcode = trim((string) ($_GET['barcode'] ?? ''));
    if ($barcode === '') {
        http_response_code(400);
        return ['error' => 'barcode required'];
    }

    $stmt = $db->prepare(
        "SELECT barcode, bucket, name, brand, n_retailers,
                pnp_price, checkers_price, woolworths_price, min_price, max_price
         FROM mvp_catalogue WHERE barcode = ? LIMIT 1"
    );
    $stmt->execute([$barcode]);
    $row = $stmt->fetch();
    if (!$row) {
        http_response_code(404);
        return ['error' => 'Product not found'];
    }

    $cat = $db->prepare(
        "SELECT retailer, product_key, name, brand, price, was_price, image_url, url, bucket
         FROM catalogue
         WHERE barcode_norm = ? AND is_instore_bc = 0
         ORDER BY retailer"
    );
    $cat->execute([$barcode]);
    $offers = $cat->fetchAll();

    $image = null;
    foreach ($offers as $o) {
        if (!empty($o['image_url'])) {
            $image = $o['image_url'];
            break;
        }
    }

    $product = map_product($row + ['image_url' => $image]);
    $product['offers'] = array_map(static function (array $o): array {
        return [
            'retailer'    => $o['retailer'],
            'product_key' => $o['product_key'],
            'name'        => $o['name'],
            'brand'       => $o['brand'],
            'price'       => $o['price'] !== null ? (float) $o['price'] : null,
            'was_price'   => $o['was_price'] !== null ? (float) $o['was_price'] : null,
            'image_url'   => $o['image_url'],
            'url'         => $o['url'],
            'bucket'      => $o['bucket'],
        ];
    }, $offers);

    return ['product' => $product];
}

function list_deals(PDO $db): array
{
    $limit = min(30, max(1, (int) ($_GET['limit'] ?? 12)));
    $sql = "SELECT m.barcode, m.bucket, m.name, m.brand, m.n_retailers,
                   m.pnp_price, m.checkers_price, m.woolworths_price,
                   m.min_price, m.max_price,
                   (SELECT c.image_url FROM catalogue c
                    WHERE c.barcode_norm = m.barcode AND c.image_url IS NOT NULL
                    AND c.image_url <> '' LIMIT 1) AS image_url
            FROM mvp_catalogue m
            WHERE m.min_price IS NOT NULL AND m.max_price IS NOT NULL
              AND m.max_price > m.min_price
            ORDER BY (m.max_price - m.min_price) DESC
            LIMIT $limit";
    $rows = $db->query($sql)->fetchAll();
    return ['deals' => array_map('map_product', $rows)];
}

function get_stats(PDO $db): array
{
    $mvp = (int) $db->query('SELECT COUNT(*) FROM mvp_catalogue')->fetchColumn();
    $cat = (int) $db->query('SELECT COUNT(*) FROM catalogue')->fetchColumn();
    return [
        'mvp_products'    => $mvp,
        'catalogue_rows'  => $cat,
        'retailers'       => ['pnp', 'checkers', 'woolworths'],
    ];
}

function map_product(array $row): array
{
    $min = $row['min_price'] !== null ? (float) $row['min_price'] : null;
    $max = $row['max_price'] !== null ? (float) $row['max_price'] : null;
    $save = ($min !== null && $max !== null) ? round($max - $min, 2) : 0.0;

    $prices = [];
    foreach (['pnp' => 'pnp_price', 'checkers' => 'checkers_price', 'woolworths' => 'woolworths_price'] as $r => $col) {
        if ($row[$col] !== null) {
            $prices[] = [
                'retailer' => $r,
                'price'    => (float) $row[$col],
                'cheapest' => $min !== null && abs((float) $row[$col] - $min) < 0.005,
            ];
        }
    }

    return [
        'barcode'     => $row['barcode'],
        'bucket'      => normalize_bucket($row['bucket'] ?? null),
        'bucket_raw'  => $row['bucket'] ?? null,
        'name'        => $row['name'],
        'brand'       => $row['brand'],
        'n_retailers' => isset($row['n_retailers']) ? (int) $row['n_retailers'] : count($prices),
        'min_price'   => $min,
        'max_price'   => $max,
        'save'        => $save,
        'image_url'   => $row['image_url'] ?? null,
        'prices'      => $prices,
    ];
}
